Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default defineConfig({
{text: 'Facebook', link: '/providers/facebook'},
{text: 'Pinterest', link: '/providers/pinterest'},
{text: 'Microsoft Ads', link: '/providers/microsoft-ads'},
{text: 'OpenAI Ads', link: '/providers/openai-ads'},
{text: 'Criteo', link: '/providers/criteo'},
{text: 'Emarsys', link: '/providers/emarsys'},
{text: 'Mixpanel', link: '/providers/mixpanel'},
Expand Down
1 change: 1 addition & 0 deletions docs/providers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Each provider is a self-contained module under `pkg/provider/` that knows how to
| [Facebook](./facebook) | — | ✔ | ad | AddToCart, Purchase, ... |
| [Pinterest](./pinterest) | — | ✔ | analytics | AddToCart, Purchase, ... |
| [Microsoft Ads](./microsoft-ads) | — | ✔ | ad | conversions |
| [OpenAI Ads](./openai-ads) | — | ✔ | ad | conversions |
| [Criteo](./criteo) | — | ✔ | — | AddToCart, Purchase, ... |
| [Emarsys](./emarsys) | — | ✔ | analytics | Purchase, ViewItem, ... |
| [Mixpanel](./mixpanel) | — | ✔ | analytics | PageView, Purchase, ... |
Expand Down
49 changes: 49 additions & 0 deletions docs/providers/openai-ads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# OpenAI Ads

OpenAI Ads server-side conversion tracking via the Conversions API.

::: warning
This provider relies on the **OpenAI Ads Conversions API by Stape** tag template. Install it into your server
container from the GTM Community Template Gallery before provisioning, or point `templates.conversionsApiTag` at a
local copy.
:::

## Configuration

```yaml
openAiAds:
enabled: true
pixelId: ''
apiKey: ''
templates:
conversionsApiTag: ''
googleConsent:
enabled: true
mode: ad_storage
conversion:
enabled: true
validateOnly: false
serverContainer:
directory: .
settings:
page_view:
eventName: page_viewed
packages:
- path: github.com/foomo/sesamy-go/pkg/event
types:
- Purchase
```

| Field | Purpose |
|-------|---------|
| `pixelId` | OpenAI Ads pixel ID. |
| `apiKey` | OpenAI Ads API key. |
| `googleConsent.mode` | Defaults to `ad_storage`. |
| `conversion.validateOnly` | Validate events without persisting them. Applies to every event. |
| `conversion.serverContainer.settings.<event>.eventName` | Standard OpenAI Ads event name. Defaults to the event name. |
| `conversion.serverContainer.packages` | Events to wire conversion tags for. |
| `templates.conversionsApiTag` | Path to a custom conversions api tag template file. Empty (default) uses the manually installed one. |

::: tip
Field names are based on the current schema. Run `sesamy config -c sesamy.yaml` to see the resolved values your build will use.
:::
1 change: 1 addition & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The full top-level schema of `sesamy.yaml` (version `1.1`). For provider-specifi
| `facebook` | object | [Facebook provider config](/providers/facebook). |
| `pinterest` | object | [Pinterest provider config](/providers/pinterest). |
| `microsoftAds` | object | [Microsoft Ads provider config](/providers/microsoft-ads). |
| `openAiAds` | object | [OpenAI Ads provider config](/providers/openai-ads). |
| `criteo` | object | [Criteo provider config](/providers/criteo). |
| `emarsys` | object | [Emarsys provider config](/providers/emarsys). |
| `mixpanel` | object | [Mixpanel provider config](/providers/mixpanel). |
Expand Down
9 changes: 9 additions & 0 deletions internal/cli/provision/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
googletagmanagerprovider "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager"
microsoftadsprovider "github.com/foomo/sesamy-cli/pkg/provider/microsoftads"
mixpanelprovider "github.com/foomo/sesamy-cli/pkg/provider/mixpanel"
openaiadsprovider "github.com/foomo/sesamy-cli/pkg/provider/openaiads"
pinterestprovider "github.com/foomo/sesamy-cli/pkg/provider/pinterest"
tracifyprovider "github.com/foomo/sesamy-cli/pkg/provider/tracify"
umamiprovider "github.com/foomo/sesamy-cli/pkg/provider/umami"
Expand Down Expand Up @@ -156,6 +157,14 @@ func NewServer(l *slog.Logger) *cobra.Command {
}
}

if cfg.OpenAIAds.Enabled && utils.Tag(openaiadsprovider.Tag, tags) {
l.Info("🅿️ Running provider", "name", openaiadsprovider.Name, "tag", openaiadsprovider.Tag)

if err := openaiadsprovider.Server(cmd.Context(), l, tm, cfg.OpenAIAds); err != nil {
return errors.Wrap(err, "failed to provision openaiads")
}
}

if cfg.Mixpanel.Enabled && utils.Tag(mixpanelprovider.Tag, tags) {
l.Info("🅿️ Running provider", "name", mixpanelprovider.Name, "tag", mixpanelprovider.Tag)

Expand Down
2 changes: 2 additions & 0 deletions internal/cli/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/foomo/sesamy-cli/pkg/provider/hotjar"
"github.com/foomo/sesamy-cli/pkg/provider/microsoftads"
"github.com/foomo/sesamy-cli/pkg/provider/mixpanel"
"github.com/foomo/sesamy-cli/pkg/provider/openaiads"
"github.com/foomo/sesamy-cli/pkg/provider/pinterest"
"github.com/foomo/sesamy-cli/pkg/provider/tracify"
"github.com/foomo/sesamy-cli/pkg/provider/umami"
Expand Down Expand Up @@ -46,6 +47,7 @@ func NewTags(l *slog.Logger) *cobra.Command {
hotjar.Name: hotjar.Tag,
microsoftads.Name: microsoftads.Tag,
mixpanel.Name: mixpanel.Tag,
openaiads.Name: openaiads.Tag,
tracify.Name: tracify.Tag,
umami.Name: umami.Tag,
pinterest.Name: pinterest.Tag,
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Config struct {
Facebook Facebook `json:"facebook" yaml:"facebook"`
// MicrosoftAds provider settings
MicrosoftAds MicrosoftAds `json:"microsoftAds" yaml:"microsoftAds"`
// OpenAIAds provider settings
OpenAIAds OpenAIAds `json:"openaiAds" yaml:"openaiAds"`
// Mixpanel provider settings
Mixpanel Mixpanel `json:"mixpanel" yaml:"mixpanel"`
// Pinterest provider settings
Expand Down
21 changes: 21 additions & 0 deletions pkg/config/openaiads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package config

type OpenAIAds struct {
// Enable provider
Enabled bool `json:"enabled" yaml:"enabled"`
// Google Consent settings
GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"`
// OpenAI Ads pixel id
PixelID string `json:"pixelId" yaml:"pixelId"`
// OpenAI Ads api key
APIKey string `json:"apiKey" yaml:"apiKey"`
// OpenAI Ads Conversion settings
Conversion OpenAIAdsConversion `json:"conversion" yaml:"conversion"`
// Custom tag template settings
Templates OpenAIAdsTemplates `json:"templates" yaml:"templates"`
}

type OpenAIAdsTemplates struct {
// Path to a custom conversions api tag template file, defaults to the manually installed one
ConversionsAPITag string `json:"conversionsApiTag" yaml:"conversionsApiTag"`
}
32 changes: 32 additions & 0 deletions pkg/config/openaiadsconversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package config

import (
"github.com/foomo/gocontemplate/pkg/contemplate"
)

type OpenAIAdsConversion struct {
// Enable provider
Enabled bool `json:"enabled" yaml:"enabled"`
// Only validate the events without persisting them
ValidateOnly bool `json:"validateOnly" yaml:"validateOnly"`
// Google Tag Manager server container settings
ServerContainer OpenAIAdsServerContainer `json:"serverContainer" yaml:"serverContainer"`
}

type OpenAIAdsServerContainer struct {
contemplate.Config `json:",inline" yaml:",squash"`
Settings map[string]OpenAIAdsConversionTag `json:"settings" yaml:"settings"`
}

type OpenAIAdsConversionTag struct {
// Standard event name sent to OpenAI Ads, defaults to the event name
EventName string `json:"eventName" yaml:"eventName"`
}

func (s *OpenAIAdsServerContainer) Setting(eventName string) OpenAIAdsConversionTag {
if value, ok := s.Settings[eventName]; ok {
return value
}

return OpenAIAdsConversionTag{}
}
9 changes: 9 additions & 0 deletions pkg/provider/openaiads/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package openaiads

const (
Tag = "oaiads"
Name = "OpenAI Ads"
NamePixelIDConstant = "OpenAI Ads Pixel ID"
NameAPIKeyConstant = "OpenAI Ads API Key"
NameConversionsAPITagTemplate = "OpenAI Ads Conversions API by Stape"
)
83 changes: 83 additions & 0 deletions pkg/provider/openaiads/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package openaiads

import (
"context"
"log/slog"

"github.com/foomo/sesamy-cli/pkg/config"
"github.com/foomo/sesamy-cli/pkg/provider/googleconsent"
googleconsentvariable "github.com/foomo/sesamy-cli/pkg/provider/googleconsent/server/variable"
servertagx "github.com/foomo/sesamy-cli/pkg/provider/openaiads/server/tag"
"github.com/foomo/sesamy-cli/pkg/provider/openaiads/server/trigger"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template"
commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable"
"github.com/foomo/sesamy-cli/pkg/utils"
"github.com/pkg/errors"
tagmanager2 "google.golang.org/api/tagmanager/v2"
)

func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg config.OpenAIAds) error {
folder, err := tm.UpsertFolder(ctx, "Sesamy - "+Name)
if err != nil {
return err
}

pixelID, err := tm.UpsertVariable(ctx, folder, commonvariable.NewConstant(NamePixelIDConstant, cfg.PixelID))
if err != nil {
return err
}

apiKey, err := tm.UpsertVariable(ctx, folder, commonvariable.NewConstant(NameAPIKeyConstant, cfg.APIKey))
if err != nil {
return err
}

if cfg.Conversion.Enabled {
tagTemplate, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.ConversionsAPITag, NameConversionsAPITagTemplate, func() (*tagmanager2.CustomTemplate, error) {
return tm.LookupTemplate(ctx, NameConversionsAPITagTemplate)
})
if err != nil {
if errors.Is(err, tagmanager.ErrNotFound) {
l.Warn("Please install the '" + NameConversionsAPITagTemplate + "' Tag Template manually first")
}

return err
}

{ // create tags
eventParameters, err := utils.LoadEventParams(ctx, cfg.Conversion.ServerContainer.Config)
if err != nil {
return err
}

for event := range eventParameters {
var eventTriggerOpts []trigger.ConversionEventOption

if cfg.GoogleConsent.Enabled {
if err := googleconsent.ServerEnsure(ctx, tm); err != nil {
return err
}

consentVariable, err := tm.LookupVariable(ctx, googleconsentvariable.GoogleConsentModeName(cfg.GoogleConsent.Mode))
if err != nil {
return err
}

eventTriggerOpts = append(eventTriggerOpts, trigger.ConversionEventWithConsentMode(consentVariable))
}

eventTrigger, err := tm.UpsertTrigger(ctx, folder, trigger.NewConversionEvent(event, eventTriggerOpts...))
if err != nil {
return errors.Wrap(err, "failed to upsert event trigger: "+event)
}

if _, err := tm.UpsertTag(ctx, folder, servertagx.NewConversion(event, pixelID, apiKey, tagTemplate, cfg.Conversion.ValidateOnly, cfg.Conversion.ServerContainer.Setting(event), eventTrigger)); err != nil {
return err
}
}
}
}

return nil
}
125 changes: 125 additions & 0 deletions pkg/provider/openaiads/server/tag/conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package tag

import (
"strconv"

"github.com/foomo/sesamy-cli/pkg/config"
"github.com/foomo/sesamy-cli/pkg/utils"
"google.golang.org/api/tagmanager/v2"
)

func ConversionName(v string) string {
return "OpenAI Ads Conversion - " + v
}

func NewConversion(name string, pixelID, apiKey *tagmanager.Variable, template *tagmanager.CustomTemplate, validateOnly bool, settings config.OpenAIAdsConversionTag, triggers ...*tagmanager.Trigger) *tagmanager.Tag {
eventName := settings.EventName
eventNameSetup := "standard"
if eventName == "" {
eventName = name
eventNameSetup = "inherit"
}

return &tagmanager.Tag{
FiringTriggerId: utils.TriggerIDs(triggers),
Name: ConversionName(name),
TagFiringOption: "oncePerEvent",
Parameter: []*tagmanager.Parameter{
{
Key: "pixelId",
Type: "template",
Value: "{{" + pixelID.Name + "}}",
},
{
Key: "apiKey",
Type: "template",
Value: "{{" + apiKey.Name + "}}",
},
{
Key: "eventNameSetup",
Type: "template",
Value: eventNameSetup,
},
{
Key: "eventNameStandard",
Type: "template",
Value: eventName,
},
{
Key: "actionSource",
Type: "template",
Value: "web",
},
{
Key: "adStorageConsent",
Type: "template",
Value: "optional",
},
{
Key: "validateOnly",
Type: "boolean",
Value: strconv.FormatBool(validateOnly),
},
{
Key: "autoMapEventParameters",
Type: "boolean",
Value: "true",
},
{
Key: "autoMapUserDataParameters",
Type: "boolean",
Value: "true",
},
{
Key: "autoMapServerEventDataParameters",
Type: "boolean",
Value: "true",
},
{
Key: "useOptimisticScenario",
Type: "boolean",
Value: "false",
},
{
Key: "itemIdKey",
Type: "template",
},
{
Key: "setBrowserIdCookie",
Type: "boolean",
Value: "true",
},
{
Key: "setClickIdCookie",
Type: "boolean",
Value: "true",
},
{
Key: "cookieDomain",
Type: "template",
Value: "auto",
},
{
Key: "cookieExpiration",
Type: "template",
Value: "30",
},
{
Key: "cookieExpirationBrowserId",
Type: "template",
Value: "365",
},
{
Key: "cookieHttpOnly",
Type: "boolean",
Value: "true",
},
{
Key: "cookieSameSite",
Type: "template",
Value: "lax",
},
},
Type: utils.TemplateType(template),
}
}
Loading