Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cdp): add snapchat ads destination #26173

Closed
wants to merge 11 commits into from
Binary file added frontend/public/services/snapchat-ads.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions posthog/cdp/templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
TemplateGoogleCloudStorageMigrator,
)
from .airtable.template_airtable import template as airtable
from .snapchat_ads.template_snapchat_ads import template as snapchat_ads
from ._internal.template_broadcast import template_new_broadcast as _broadcast

HOG_FUNCTION_TEMPLATES = [
Expand Down Expand Up @@ -78,6 +79,7 @@
salesforce_create,
salesforce_update,
sendgrid,
snapchat_ads,
zapier,
zendesk,
]
Expand Down
141 changes: 141 additions & 0 deletions posthog/cdp/templates/snapchat_ads/template_snapchat_ads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from posthog.cdp.templates.hog_function_template import HogFunctionTemplate

template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
type="destination",
id="template-snapchat-ads",
name="Snapchat Ads Conversions",
description="Send conversion events to Snapchat Ads",
icon_url="/static/services/snapchat-ads.png",
MarconLP marked this conversation as resolved.
Show resolved Hide resolved
category=["Advertisement"],
hog="""
let body := {
'data': [
{
'event_name': inputs.eventName,
'action_source': inputs.actionSource,
'event_time': inputs.eventTime,
'user_data': {},
'custom_data': {}
}
]
}

if (not empty(event.properties.$current_url)) body.data.1.event_source_url := event.properties.$current_url

for (let key, value in inputs.userData) {
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 is an empty string hashed
if (not empty(value) and value != 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') {
body.data.1.user_data[key] := value
}
}

for (let key, value in inputs.customData) {
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 is an empty string hashed
if (not empty(value) and value != 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') {
MarconLP marked this conversation as resolved.
Show resolved Hide resolved
body.data.1.custom_data[key] := value
}
}

let res := fetch(f'https://tr.snapchat.com/v3/{inputs.pixelId}/events?access_token={inputs.accessToken}', {
'method': 'POST',
'headers': {
'Content-Type': 'application/json',
},
'body': body
})
if (res.status >= 400) {
throw Error(f'Error from tr.snapchat.com (status {res.status}): {res.body}')
}
""".strip(),
inputs_schema=[
{
"key": "accessToken",
"type": "string",
"label": "Access token",
"description": "Check out this page on how to obtain such a token: https://developers.snap.com/api/marketing-api/Conversions-API/GetStarted#access-token",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use the oauth integration? Feels way nicer https://developers.snap.com/snap-kit/login-kit/Tutorials/web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer, but it would require two separate oauth apps since each only supports a single redirect URL. Doesn't seem to be worth the effort as the token is pretty easy to generate, but can look into it if you think it's worth it.

"secret": True,
"required": True,
},
{
"key": "pixelId",
"type": "string",
"label": "Pixel ID",
"description": "You must obtain a Pixel ID to use the Conversions API. If you’ve already set up a Pixel for your website, we recommend that you use the same Pixel ID for your browser and server events.",
"secret": False,
"required": True,
},
{
"key": "eventName",
"type": "string",
"label": "Event name",
"description": "A standard event or custom event name.",
"default": "{event.event}",
"secret": False,
"required": True,
},
{
"key": "eventTime",
"type": "string",
"label": "Event time",
"description": "A Unix timestamp in seconds indicating when the actual event occurred. You must send this date in GMT time zone.",
"default": "{toUnixTimestampMilli(event.timestamp)}",
"secret": False,
"required": True,
},
{
"key": "actionSource",
"label": "Action source",
"type": "choice",
"choices": [
{
"label": "WEB - Conversion was made on your website.",
"value": "WEB",
},
{
"label": "MOBILE_APP - Conversion was made on your mobile app.",
"value": "MOBILE_APP",
},
{
"label": "OFFLINE - Conversion happened in a way that is not listed.",
"value": "OFFLINE",
},
],
"description": "This field allows you to specify where your conversions occurred. Knowing where your events took place helps ensure your ads go to the right people.",
"default": "website",
"secret": False,
"required": True,
},
{
"key": "userData",
"type": "dictionary",
"label": "User data",
"description": "A map that contains customer information data. See this page for options: https://developers.snap.com/api/marketing-api/Conversions-API/Parameters#user-data-parameters",
"default": {
"em": "{sha256Hex(person.properties.email ?? '')}",
"ph": "{sha256Hex(person.properties.phone ?? '')}",
"sc_click_id": "{person.properties.sccid ?? person.properties.$initial_sccid ?? ''}",
},
"secret": False,
"required": True,
},
{
"key": "customData",
"type": "dictionary",
"label": "Custom data",
"description": "A map that contains custom data. See this page for options: https://developers.snap.com/api/marketing-api/Conversions-API/Parameters#custom-data-parameters",
"default": {
"currency": "USD",
"value": "{event.properties.price}",
"event_id": "{event.uuid}",
},
"secret": False,
"required": True,
},
],
filters={
"events": [],
"actions": [],
"filter_test_accounts": True,
},
)
69 changes: 69 additions & 0 deletions posthog/cdp/templates/snapchat_ads/test_template_snapchat_ads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from inline_snapshot import snapshot
from posthog.cdp.templates.helpers import BaseHogFunctionTemplateTest
from posthog.cdp.templates.snapchat_ads.template_snapchat_ads import (
template as template_snapchat_ads,
)


class TestTemplateSnapchatAds(BaseHogFunctionTemplateTest):
template = template_snapchat_ads

def _inputs(self, **kwargs):
inputs = {
"accessToken": "accessToken12345",
"pixelId": "pixel12345",
"eventName": "purchase",
"eventTime": "1728812163",
"actionSource": "WEB",
"userData": {
"em": "3edfaed7454eedb3c72bad566901af8bfbed1181816dde6db91dfff0f0cffa98",
"ph": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"customData": {
"currency": "USD",
"price": "1500",
"event_id": "49ff3d7c-359d-4f45-960e-6cda29f1beea",
},
}
inputs.update(kwargs)
return inputs

def test_function_works(self):
self.run_function(
self._inputs(),
globals={
"event": {
"uuid": "49ff3d7c-359d-4f45-960e-6cda29f1beea",
"properties": {
"$current_url": "https://posthog.com/cdp",
},
},
},
)
assert self.get_mock_fetch_calls()[0] == snapshot(
(
"https://tr.snapchat.com/v3/pixel12345/events?access_token=accessToken12345",
{
"method": "POST",
"headers": {
"Content-Type": "application/json",
},
"body": {
"data": [
{
"event_name": "purchase",
"action_source": "WEB",
"event_time": "1728812163",
"user_data": {"em": "3edfaed7454eedb3c72bad566901af8bfbed1181816dde6db91dfff0f0cffa98"},
"custom_data": {
"currency": "USD",
"price": "1500",
"event_id": "49ff3d7c-359d-4f45-960e-6cda29f1beea",
},
"event_source_url": "https://posthog.com/cdp",
}
]
},
},
)
)
Loading