diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts new file mode 100644 index 0000000000..6872c90572 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts @@ -0,0 +1,158 @@ +import { InputField } from '@segment/actions-core' + +export const commonFields: Record = { + event: { + label: 'Event Name', + type: 'string', + required: true, + description: + 'Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel SDK documentation](https://business-api.tiktok.com/portal/docs?id=1739585696931842) for accepted event names.' + }, + event_id: { + label: 'Event ID', + type: 'string', + description: 'Any hashed ID that can identify a unique user/session.', + default: { + '@path': '$.messageId' + } + }, + phone_number: { + label: 'Phone Number', + description: + 'A single phone number in E.164 standard format. TikTok Pixel will hash this value before sending to TikTok. e.g. +14150000000. Segment will hash this value before sending to TikTok.', + type: 'string', + multiple: true, + default: { + '@if': { + exists: { '@path': '$.properties.phone' }, + then: { '@path': '$.properties.phone' }, + else: { '@path': '$.context.traits.phone' } + } + } + }, + email: { + label: 'Email', + description: 'A single email address. TikTok Pixel will be hash this value before sending to TikTok.', + type: 'string', + multiple: true, + default: { + '@if': { + exists: { '@path': '$.properties.email' }, + then: { '@path': '$.properties.email' }, + else: { '@path': '$.context.traits.email' } + } + } + }, + order_id: { + label: 'Order ID', + type: 'string', + description: 'Order ID of the transaction.', + default: { + '@path': '$.properties.order_id' + } + }, + shop_id: { + label: 'Shop ID', + type: 'string', + description: 'Shop ID of the transaction.', + default: { + '@path': '$.properties.shop_id' + } + }, + external_id: { + label: 'External ID', + description: + 'Uniquely identifies the user who triggered the conversion event. TikTok Pixel will hash this value before sending to TikTok.', + type: 'string', + multiple: true, + default: { + '@if': { + exists: { '@path': '$.userId' }, + then: { '@path': '$.userId' }, + else: { '@path': '$.anonymousId' } + } + } + }, + contents: { + label: 'Contents', + type: 'object', + multiple: true, + description: 'Related item details for the event.', + properties: { + price: { + label: 'Price', + description: 'Price of the item.', + type: 'number' + }, + quantity: { + label: 'Quantity', + description: 'Number of items.', + type: 'number' + }, + content_category: { + label: 'Content Category', + description: 'Category of the product item.', + type: 'string' + }, + content_id: { + label: 'Content ID', + description: 'ID of the product item.', + type: 'string' + }, + content_name: { + label: 'Content Name', + description: 'Name of the product item.', + type: 'string' + }, + brand: { + label: 'Brand', + description: 'Brand name of the product item.', + type: 'string' + } + } + }, + content_type: { + label: 'Content Type', + description: + 'Type of the product item. When the `content_id` in the `Contents` field is specified as a `sku_id`, set this field to `product`. When the `content_id` in the `Contents` field is specified as an `item_group_id`, set this field to `product_group`.', + type: 'string', + choices: [ + { label: 'product', value: 'product' }, + { label: 'product_group', value: 'product_group' } + ], + default: 'product' + }, + currency: { + label: 'Currency', + type: 'string', + description: 'Currency for the value specified as ISO 4217 code.', + default: { + '@path': '$.properties.currency' + } + }, + value: { + label: 'Value', + type: 'number', + description: 'Value of the order or items sold.', + default: { + '@if': { + exists: { '@path': '$.properties.value' }, + then: { '@path': '$.properties.value' }, + else: { '@path': '$.properties.revenue' } + } + } + }, + description: { + label: 'Description', + type: 'string', + description: 'A string description of the web event.' + }, + query: { + label: 'Query', + type: 'string', + description: 'The text string that was searched for.', + default: { + '@path': '$.properties.query' + } + } +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/generated-types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/generated-types.ts index 608ad2bb52..31295d97c6 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/generated-types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/generated-types.ts @@ -5,6 +5,10 @@ export interface Settings { * Your TikTok Pixel ID. Please see TikTok's [Pixel documentation](https://ads.tiktok.com/marketing_api/docs?id=1739583652957185) for information on how to find this value. */ pixelCode: string + /** + * In order to help facilitate advertiser's compliance with the right to opt-out of sale and sharing of personal data under certain U.S. state privacy laws, TikTok offers a Limited Data Use ("LDU") feature. For more information, please refer to TikTok's [documentation page](https://business-api.tiktok.com/portal/docs?id=1770092377990145). + */ + ldu?: boolean /** * Important! Changing this setting may block data collection to Segment if not done correctly. Select "true" to use an existing TikTok Pixel which is already installed on your website. The Pixel MUST be installed on your website when this is set to "true" or all data collection to Segment may fail. */ diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/index.ts index 6c5c13a28b..3b54707bef 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/index.ts @@ -19,11 +19,17 @@ const productProperties = { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { '@path': '$.product_id' + }, + content_name: { + '@path': '$.name' + }, + brand: { + '@path': '$.brand' } } @@ -57,9 +63,49 @@ export const destination: BrowserDestinationDefinition = slug: 'actions-tiktok-pixel', mode: 'device', presets: [ + { + name: 'Complete Payment', + subscribe: 'event = "Order Completed"', + partnerAction: 'reportWebEvent', + mapping: { + ...multiProductContents, + event: 'CompletePayment' + }, + type: 'automatic' + }, + { + name: 'Contact', + subscribe: 'event = "Callback Started"', + partnerAction: 'reportWebEvent', + mapping: { + ...defaultValues(reportWebEvent.fields), + event: 'Contact' + }, + type: 'automatic' + }, + { + name: 'Subscribe', + subscribe: 'event = "Subscription Created"', + partnerAction: 'reportWebEvent', + mapping: { + ...defaultValues(reportWebEvent.fields), + event: 'Subscribe' + }, + type: 'automatic' + }, + { + name: 'Submit Form', + subscribe: 'event = "Form Submitted"', + partnerAction: 'reportWebEvent', + mapping: { + ...defaultValues(reportWebEvent.fields), + event: 'SubmitForm' + }, + type: 'automatic' + }, { name: 'View Content', - subscribe: 'type="page"', + subscribe: 'event = "Product Viewed"', partnerAction: 'reportWebEvent', mapping: { ...singleProductContents, @@ -67,6 +113,16 @@ export const destination: BrowserDestinationDefinition = }, type: 'automatic' }, + { + name: 'Click Button', + subscribe: 'event = "Product Clicked"', + partnerAction: 'reportWebEvent', + mapping: { + ...singleProductContents, + event: 'ClickButton' + }, + type: 'automatic' + }, { name: 'Search', subscribe: 'event = "Products Searched"', @@ -119,13 +175,33 @@ export const destination: BrowserDestinationDefinition = }, { name: 'Place an Order', - subscribe: 'event = "Order Completed"', + subscribe: 'event = "Order Placed"', partnerAction: 'reportWebEvent', mapping: { ...multiProductContents, event: 'PlaceAnOrder' }, type: 'automatic' + }, + { + name: 'Download', + subscribe: 'event = "Download Link Clicked"', + partnerAction: 'reportWebEvent', + mapping: { + ...defaultValues(reportWebEvent.fields), + event: 'Download' + }, + type: 'automatic' + }, + { + name: 'Complete Registration', + subscribe: 'event = "Signed Up"', + partnerAction: 'reportWebEvent', + mapping: { + ...defaultValues(reportWebEvent.fields), + event: 'CompleteRegistration' + }, + type: 'automatic' } ], settings: { @@ -136,16 +212,24 @@ export const destination: BrowserDestinationDefinition = "Your TikTok Pixel ID. Please see TikTok's [Pixel documentation](https://ads.tiktok.com/marketing_api/docs?id=1739583652957185) for information on how to find this value.", required: true }, - useExistingPixel: { - label: 'Use Existing Pixel', + ldu: { + label: 'Limited Data Use', type: 'boolean', description: - 'Important! Changing this setting may block data collection to Segment if not done correctly. Select "true" to use an existing TikTok Pixel which is already installed on your website. The Pixel MUST be installed on your website when this is set to "true" or all data collection to Segment may fail.' + 'In order to help facilitate advertiser\'s compliance with the right to opt-out of sale and sharing of personal data under certain U.S. state privacy laws, TikTok offers a Limited Data Use ("LDU") feature. For more information, please refer to TikTok\'s [documentation page](https://business-api.tiktok.com/portal/docs?id=1770092377990145).' + }, + useExistingPixel: { + // TODO: HOW TO DELETE (reusing will not include Segment Partner name) + label: '[Deprecated] Use Existing Pixel', + type: 'boolean', + default: false, + required: false, + description: 'Deprecated. Please do not provide any value.' } }, initialize: async ({ settings }, deps) => { if (!settings.useExistingPixel) { - initScript(settings.pixelCode) + initScript(settings) } await deps.resolveWhen(() => window.ttq != null, 100) return window.ttq diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/init-script.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/init-script.ts index 8e3794db99..d99bf21eee 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/init-script.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/init-script.ts @@ -1,6 +1,6 @@ /* eslint-disable */ // @ts-nocheck -export function initScript(pixelCode) { +export function initScript(settings) { !(function (w, d, t) { w.TiktokAnalyticsObject = t var ttq = (w[t] = w[t] || []) @@ -44,7 +44,9 @@ export function initScript(pixelCode) { a.parentNode.insertBefore(o, a) }) - ttq.load(pixelCode) + ttq.load(settings.pixelCode, { + limited_data_use: settings.ldu ? settings.ldu : false + }) ttq.page() })(window, document, 'ttq') } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/formatter.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/formatter.ts index e7f9a54e96..f696ddb9a4 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/formatter.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/formatter.ts @@ -16,3 +16,8 @@ export function formatPhone(phone: string | undefined): string | undefined { formattedPhone = formattedPhone.substring(0, 15) return formattedPhone } + +export function handleArrayInput(array: string[] | undefined): string { + if (!array || array.length == 0) return '' + return array[0] +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts index f0f1dbf9dd..88bd3342bd 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts @@ -2,7 +2,7 @@ export interface Payload { /** - * Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel documentation](https://ads.tiktok.com/marketing_api/docs?id=1739585696931842) for accepted event names. + * Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel SDK documentation](https://business-api.tiktok.com/portal/docs?id=1739585696931842) for accepted event names. */ event: string /** @@ -10,19 +10,27 @@ export interface Payload { */ event_id?: string /** - * Phone number of the user who triggered the conversion event, in E.164 standard format, e.g. +14150000000. Segment will hash this value before sending to TikTok. + * A single phone number in E.164 standard format. TikTok Pixel will hash this value before sending to TikTok. e.g. +14150000000. Segment will hash this value before sending to TikTok. */ - phone_number?: string + phone_number?: string[] /** - * Email address of the user who triggered the conversion event. Segment will hash this value before sending to TikTok. + * A single email address. TikTok Pixel will be hash this value before sending to TikTok. */ - email?: string + email?: string[] /** - * Uniquely identifies the user who triggered the conversion event. Segment will hash this value before sending to TikTok. + * Order ID of the transaction. */ - external_id?: string + order_id?: string /** - * Related items in a web event. + * Shop ID of the transaction. + */ + shop_id?: string + /** + * Uniquely identifies the user who triggered the conversion event. TikTok Pixel will hash this value before sending to TikTok. + */ + external_id?: string[] + /** + * Related item details for the event. */ contents?: { /** @@ -34,14 +42,26 @@ export interface Payload { */ quantity?: number /** - * Type of the product item. + * Category of the product item. */ - content_type?: string + content_category?: string /** * ID of the product item. */ content_id?: string + /** + * Name of the product item. + */ + content_name?: string + /** + * Brand name of the product item. + */ + brand?: string }[] + /** + * Type of the product item. When the `content_id` in the `Contents` field is specified as a `sku_id`, set this field to `product`. When the `content_id` in the `Contents` field is specified as an `item_group_id`, set this field to `product_group`. + */ + content_type?: string /** * Currency for the value specified as ISO 4217 code. */ diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts index a6a5b404f5..95358a94f4 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts @@ -1,8 +1,9 @@ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { formatPhone } from './formatter' +import { formatPhone, handleArrayInput } from './formatter' import { TikTokPixel } from '../types' +import { commonFields } from '../common_fields' const action: BrowserActionDefinition = { title: 'Report Web Event', @@ -11,133 +12,14 @@ const action: BrowserActionDefinition = { platform: 'web', defaultSubscription: 'type = "track"', fields: { - event: { - label: 'Event Name', - type: 'string', - required: true, - description: - 'Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel documentation](https://ads.tiktok.com/marketing_api/docs?id=1739585696931842) for accepted event names.' - }, - event_id: { - label: 'Event ID', - type: 'string', - description: 'Any hashed ID that can identify a unique user/session.', - default: { - '@path': '$.messageId' - } - }, - // PII Fields - These fields must be hashed using SHA 256 and encoded as websafe-base64. - phone_number: { - label: 'Phone Number', - description: - 'Phone number of the user who triggered the conversion event, in E.164 standard format, e.g. +14150000000. Segment will hash this value before sending to TikTok.', - type: 'string', - default: { - '@if': { - exists: { '@path': '$.properties.phone' }, - then: { '@path': '$.properties.phone' }, - else: { '@path': '$.traits.phone' } - } - } - }, - email: { - label: 'Email', - description: - 'Email address of the user who triggered the conversion event. Segment will hash this value before sending to TikTok.', - type: 'string', - format: 'email', - default: { - '@if': { - exists: { '@path': '$.properties.email' }, - then: { '@path': '$.properties.email' }, - else: { '@path': '$.traits.email' } - } - } - }, - external_id: { - label: 'External ID', - description: - 'Uniquely identifies the user who triggered the conversion event. Segment will hash this value before sending to TikTok.', - type: 'string', - default: { - '@if': { - exists: { '@path': '$.userId' }, - then: { '@path': '$.userId' }, - else: { '@path': '$.anonymousId' } - } - } - }, - contents: { - label: 'Contents', - type: 'object', - multiple: true, - description: 'Related items in a web event.', - properties: { - price: { - label: 'Price', - description: 'Price of the item.', - type: 'number' - }, - quantity: { - label: 'Quantity', - description: 'Number of items.', - type: 'number' - }, - content_type: { - label: 'Content Type', - description: 'Type of the product item.', - type: 'string' - }, - content_id: { - label: 'Content ID', - description: 'ID of the product item.', - type: 'string' - } - } - }, - currency: { - label: 'Currency', - type: 'string', - description: 'Currency for the value specified as ISO 4217 code.', - default: { - '@path': '$.properties.currency' - } - }, - value: { - label: 'Value', - type: 'number', - description: 'Value of the order or items sold.', - default: { - '@if': { - exists: { '@path': '$.properties.value' }, - then: { '@path': '$.properties.value' }, - else: { '@path': '$.properties.revenue' } - } - } - }, - description: { - label: 'Description', - type: 'string', - description: 'A string description of the web event.', - default: { - '@path': '$.properties.description' - } - }, - query: { - label: 'Query', - type: 'string', - description: 'The text string that was searched for.', - default: { - '@path': '$.properties.query' - } - } + ...commonFields }, perform: (ttq, { payload }) => { - if (payload.email || payload.phone_number) { + if (payload.email || payload.phone_number || payload.external_id) { ttq.identify({ - email: payload.email, - phone_number: formatPhone(payload.phone_number), - external_id: payload.external_id + email: handleArrayInput(payload.email), + phone_number: formatPhone(handleArrayInput(payload.phone_number)), + external_id: handleArrayInput(payload.external_id) }) } @@ -145,13 +27,16 @@ const action: BrowserActionDefinition = { payload.event, { contents: payload.contents ? payload.contents : [], - currency: payload.currency ? payload.currency : 'USD', // default to 'USD' - value: payload.value ? payload.value : 0, //default to 0 - description: payload.description, - query: payload.query + content_type: payload.content_type ? payload.content_type : undefined, + currency: payload.currency ? payload.currency : 'USD', + value: payload.value ? payload.value : 0, + query: payload.query ? payload.query : undefined, + description: payload.description ? payload.description : undefined, + order_id: payload.order_id ? payload.order_id : undefined, + shop_id: payload.shop_id ? payload.shop_id : undefined }, { - event_id: payload.event_id + event_id: payload.event_id ? payload.event_id : '' } ) } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts index c1ceacf601..e71a5d1529 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts @@ -13,23 +13,31 @@ export interface TikTokPixel { event: string, { contents, + content_type, currency, value, description, - query + query, + order_id, + shop_id }: { contents: | { - price?: number - quantity?: number - content_type?: string - content_id?: string + price?: number | undefined + quantity?: number | undefined + content_category?: string | undefined + content_id?: string | undefined + content_name?: string | undefined + brand?: string | undefined }[] | [] - currency: string - value: number + content_type: string | undefined + currency: string | undefined + value: number | undefined description: string | undefined query: string | undefined + order_id: string | undefined + shop_id: string | undefined }, { event_id