diff --git a/frontend/public/services/snapchat.png b/frontend/public/services/snapchat.png new file mode 100644 index 0000000000000..4fd5486e6dd77 Binary files /dev/null and b/frontend/public/services/snapchat.png differ diff --git a/posthog/cdp/site_functions.py b/posthog/cdp/site_functions.py index 3896f4f73515e..a6911168203c2 100644 --- a/posthog/cdp/site_functions.py +++ b/posthog/cdp/site_functions.py @@ -81,7 +81,12 @@ def get_transpiled_function(hog_function: HogFunction) -> str: const posthog = config.posthog; const callback = config.callback; if ('onLoad' in source) { - const r = source.onLoad({ inputs: buildInputs({}, true), posthog: posthog }); + const globals = { + person: { + properties: posthog.get_property('$stored_person_properties'), + } + } + const r = source.onLoad({ inputs: buildInputs(globals, true), posthog: posthog }); if (r && typeof r.then === 'function' && typeof r.finally === 'function') { r.catch(() => callback(false)).then(() => callback(true)) } else { callback(true) } } else { callback(true); diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index 3434fab954d16..be9044d3c069f 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -39,6 +39,10 @@ TemplateGoogleCloudStorageMigrator, ) from .airtable.template_airtable import template as airtable +from .snapchat_ads.template_snapchat_ads import ( + template as snapchat_ads, + template_site_destination as snapchat_ads_site_destination, +) from .brevo.template_brevo import template as brevo from ._siteapps.template_early_access_features import template as early_access_features from ._siteapps.template_hogdesk import template as hogdesk @@ -90,6 +94,8 @@ salesforce_create, salesforce_update, sendgrid, + snapchat_ads, + snapchat_ads_site_destination, zapier, zendesk, early_access_features, diff --git a/posthog/cdp/templates/snapchat_ads/template_snapchat_ads.py b/posthog/cdp/templates/snapchat_ads/template_snapchat_ads.py new file mode 100644 index 0000000000000..beb3aba69c930 --- /dev/null +++ b/posthog/cdp/templates/snapchat_ads/template_snapchat_ads.py @@ -0,0 +1,217 @@ +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.png", + 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) { + if (not empty(value)) { + body.data.1.user_data[key] := value + } +} + +for (let key, value in inputs.customData) { + if (not empty(value)) { + 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", + "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, + }, +) + +template_site_destination: HogFunctionTemplate = HogFunctionTemplate( + status="client-side", + type="site_destination", + id="template-snapchat-site-destination", + name="Snapchat Pixel", + description="Track how many Snapchat users interact with your website.", + icon_url="/static/services/snapchat.png", + category=["Advertisement"], + hog=""" +export async function onLoad({ inputs }) { + (function(e,t,n){if(e.snaptr)return;var a=e.snaptr=function() + {a.handleRequest?a.handleRequest.apply(a,arguments):a.queue.push(arguments)}; + a.queue=[];var s='script';r=t.createElement(s);r.async=!0; + r.src=n;var u=t.getElementsByTagName(s)[0]; + u.parentNode.insertBefore(r,u);})(window,document, + 'https://sc-static.net/scevent.min.js'); + + let userProperties = {}; + + for (const [key, value] of Object.entries(inputs.userProperties)) { + if (value) { + userProperties[key] = value; + } + }; + + snaptr('init', '{inputs.pixelId}', userProperties); +} + +export function onEvent({ inputs }) { + let eventProperties = {}; + + for (const [key, value] of Object.entries(inputs.eventProperties)) { + if (value) { + eventProperties[key] = value; + } + }; + + snaptr('track', 'PAGE_VIEW', eventProperties); +} +""".strip(), + inputs_schema=[ + { + "key": "pixelId", + "type": "string", + "label": "Pixel ID", + "description": "You must obtain a Pixel ID to use the Snapchat Pixel. 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.", + "default": "", + "secret": False, + "required": True, + }, + { + "key": "userProperties", + "type": "dictionary", + "description": "Map of Snapchat user parameters and their values. Check out this page for more details: https://businesshelp.snapchat.com/s/article/pixel-direct-implementation", + "label": "User parameters", + "default": { + "user_email": "{person.properties.email}", + "ip_address": "{person.user.ip_address}", + }, + "secret": False, + "required": False, + }, + { + "key": "eventProperties", + "type": "dictionary", + "description": "Map of Snapchat event attributes and their values. Check out this page for more details: https://businesshelp.snapchat.com/s/article/pixel-direct-implementation", + "label": "Event parameters", + "default": { + "currency": "{event.properties.currency}", + "price": "{event.properties.price}", + "client_dedup_id": "{event.uuid}", + }, + "secret": False, + "required": False, + }, + ], +) diff --git a/posthog/cdp/templates/snapchat_ads/test_template_snapchat_ads.py b/posthog/cdp/templates/snapchat_ads/test_template_snapchat_ads.py new file mode 100644 index 0000000000000..8f4f10a4b9a40 --- /dev/null +++ b/posthog/cdp/templates/snapchat_ads/test_template_snapchat_ads.py @@ -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", + } + ] + }, + }, + ) + )