Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createTestIntegration, DynamicFieldResponse } from '@segment/actions-core'
import { Features } from '@segment/actions-core/mapping-kit'
import nock from 'nock'
import { CANARY_API_VERSION, formatToE164, commonEmailValidation, convertTimestamp } from '../functions'
import { CANARY_API_VERSION, formatToE164, commonEmailValidation, convertTimestamp, timestampToEpochMicroseconds } from '../functions'
import destination from '../index'

const testDestination = createTestIntegration(destination)
Expand Down Expand Up @@ -192,3 +192,18 @@ describe('convertTimestamp', () => {
expect(result).toEqual('2025-03-11 17:57:29+00:00')
})
})

describe('timestampToEpochMicroseconds', () => {
it('should convert timestamp with milliseconds to epoch microseconds', () => {
const timestamp = '2025-10-31T12:13:51.053Z'
const result = timestampToEpochMicroseconds(timestamp)
expect(result).toEqual('1761912831053000')
})

it('should return undefined for bad timestamps', () => {
const timestamp = 'I AM NOT A TIMESTAMP - BLEEP BLOOP'
const result = timestampToEpochMicroseconds(timestamp)
expect(result).toEqual(undefined)
})
})

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ export function convertTimestamp(timestamp: string | undefined): string | undefi
return timestamp.replace(/T/, ' ').replace(/(\.\d+)?Z/, '+00:00')
}

export function timestampToEpochMicroseconds(timestamp: string): string | undefined {
const date = new Date(timestamp)
if (!isNaN(date.getTime())) {
return (date.getTime() * 1000).toString()
}
return undefined
}

export function getApiVersion(features?: Features, statsContext?: StatsContext): string {
const statsClient = statsContext?.statsClient
const tags = statsContext?.tags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export interface ClickConversionRequestObjectInterface {
gclid: string | undefined
gbraid: string | undefined
wbraid: string | undefined
sessionAttributesEncoded: string | undefined
userIpAddress?: string
sessionAttributesEncoded?: string
sessionAttributesKeyValuePairs?: { [key: string]: string }
orderId: string | undefined
userIdentifiers: UserIdentifierInterface[]
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
getApiVersion,
commonEmailValidation,
getConversionActionDynamicData,
formatPhone
formatPhone,
timestampToEpochMicroseconds
} from '../functions'
import { GOOGLE_ENHANCED_CONVERSIONS_BATCH_SIZE } from '../constants'
import { processHashing } from '../../../lib/hashing-utils'
Expand Down Expand Up @@ -55,11 +56,89 @@ const action: ActionDefinition<Settings, Payload> = {
'The click identifier for clicks associated with web conversions and originating from iOS devices starting with iOS14.',
type: 'string'
},
user_ip_address: {
label: 'User IP Address',
description: 'The IP address of the user who initiated the conversion.',
type: 'string',
default: {
'@path': '$.context.ip'
}
},
session_attributes_encoded: {
label: 'Session Attributes (Encoded)',
description:
"A base64url-encoded JSON string containing session attributes collected from the user's browser. This provides additional attribution context if gclid, gbraid, or user identifiers are missing.",
type: 'string'
"A base64url-encoded JSON string containing session attributes collected from the user's browser. Provides additional attribution context if gclid, gbraid, or user identifiers are missing. ",
type: 'string',
default: {
'@path': '$.integrations.Google Ads Conversions.session_attributes_encoded'
}
},
session_attributes_key_value_pairs: {
label: 'Session Attributes (Key Value Pairs)',
description:
"An alternative to the 'Session Attributes (Encoded)' field which can be used for Offline Conversions. If both 'Session Attributes (Encoded)' and 'Session Attributes (Key Value Pairs)' are provided, the encoded field takes precedence.",
type: 'object',
additionalProperties: false,
defaultObjectUI: 'keyvalue',
properties: {
gad_source: {
label: 'GAD Source',
description:
"An aggregate parameter served in the URL to identify the source of traffic originating from ads. See [Google's docs](https://support.google.com/google-ads/answer/16193746?sjid=2692215861659291994)",
type: 'string'
},
gad_campaignid: {
label: 'GAD Campaign ID',
description:
"The ID of the specific ad campaign that drove the ad click. See [Google's docs](https://support.google.com/google-ads/answer/16193746?sjid=2692215861659291994)",
type: 'string'
},
landing_page_url: {
label: 'Landing Page URL',
description:
'The full URL of the landing page on your website. This indicates the specific page the user first arrived on.',
type: 'string'
},
session_start_time_usec: {
label: 'Session Start Time',
description:
"The timestamp of when the user's session began on your website. This helps track the duration of user visits. The format should be a full ISO 8601 string containing microseconds.",
type: 'string',
format: 'date-time'
},
landing_page_referrer: {
label: 'Landing Page Referrer',
description:
"The URL of the webpage that linked the user to your website. This helps understand the traffic sources leading to your site. See [Google's docs](https://support.google.com/google-ads/answer/2382957?sjid=658827203196258052)",
type: 'string'
},
landing_page_user_agent: {
label: 'Landing Page User Agent',
description:
"A string that identifies the user's browser and operating system. This information can be useful for understanding the technical environment of your users.",
type: 'string'
}
},
default: {
gad_source: {
'@path': '$.properties.gad_source'
},
gad_campaignid: {
'@path': '$.properties.gad_campaignid'
},
landing_page_url: {
'@path': '$.context.page.url'
},
session_start_time_usec: {
'@path': '$.timestamp'
},
landing_page_referrer: {
'@path': '$.context.page.referrer'
},
landing_page_user_agent: {
'@path': '$.context.userAgent'
}
}
},
conversion_timestamp: {
label: 'Conversion Timestamp',
Expand Down Expand Up @@ -279,13 +358,42 @@ const action: ActionDefinition<Settings, Payload> = {
})
}

const {
session_attributes_encoded,
session_attributes_key_value_pairs: {
gad_source,
gad_campaignid,
landing_page_url,
session_start_time_usec,
landing_page_referrer,
landing_page_user_agent
} = {}
} = payload

const sessionStartTimeUsec = session_start_time_usec
? timestampToEpochMicroseconds(session_start_time_usec)
: undefined

const sessionAttributesKeyValuePairs = {
...(gad_source ? { gadSource: gad_source } : {}),
...(gad_campaignid ? { gadCampaignId: gad_campaignid } : {}),
...(landing_page_url ? { landingPageUrl: landing_page_url } : {}),
...(sessionStartTimeUsec ? { sessionStartTimeUsec } : {}),
...(landing_page_referrer ? { landingPageReferrer: landing_page_referrer } : {}),
...(landing_page_user_agent ? { landingPageUserAgent: landing_page_user_agent } : {})
}

const request_object: ClickConversionRequestObjectInterface = {
conversionAction: `customers/${settings.customerId}/conversionActions/${payload.conversion_action}`,
conversionDateTime: convertTimestamp(payload.conversion_timestamp),
gclid: payload.gclid,
gbraid: payload.gbraid,
wbraid: payload.wbraid,
sessionAttributesEncoded: payload.session_attributes_encoded,
...(payload.user_ip_address ? { userIpAddress: payload.user_ip_address } : {}),
...(session_attributes_encoded ? { sessionAttributesEncoded: session_attributes_encoded } : {}),
...(!session_attributes_encoded && Object.keys(sessionAttributesKeyValuePairs).length > 0
? { sessionAttributesKeyValuePairs }
: {}),
orderId: payload.order_id,
conversionValue: payload.value,
currencyCode: payload.currency,
Expand Down Expand Up @@ -386,13 +494,42 @@ const action: ActionDefinition<Settings, Payload> = {
})
}

const {
session_attributes_encoded,
session_attributes_key_value_pairs: {
gad_source,
gad_campaignid,
landing_page_url,
session_start_time_usec,
landing_page_referrer,
landing_page_user_agent
} = {}
} = payload

const sessionStartTimeUsec = session_start_time_usec
? timestampToEpochMicroseconds(session_start_time_usec)
: undefined

const sessionAttributesKeyValuePairs = {
...(gad_source ? { gadSource: gad_source } : {}),
...(gad_campaignid ? { gadCampaignId: gad_campaignid } : {}),
...(landing_page_url ? { landingPageUrl: landing_page_url } : {}),
...(sessionStartTimeUsec ? { sessionStartTimeUsec } : {}),
...(landing_page_referrer ? { landingPageReferrer: landing_page_referrer } : {}),
...(landing_page_user_agent ? { landingPageUserAgent: landing_page_user_agent } : {})
}

const request_object: ClickConversionRequestObjectInterface = {
conversionAction: `customers/${customerId}/conversionActions/${payload.conversion_action}`,
conversionDateTime: convertTimestamp(payload.conversion_timestamp),
gclid: payload.gclid,
gbraid: payload.gbraid,
wbraid: payload.wbraid,
sessionAttributesEncoded: payload.session_attributes_encoded,
...(payload.user_ip_address ? { userIpAddress: payload.user_ip_address } : {}),
...(session_attributes_encoded ? { sessionAttributesEncoded: session_attributes_encoded } : {}),
...(!session_attributes_encoded && Object.keys(sessionAttributesKeyValuePairs).length > 0
? { sessionAttributesKeyValuePairs }
: {}),
orderId: payload.order_id,
conversionValue: payload.value,
currencyCode: payload.currency,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading