Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
266327b
feat(reddit-conversions-api): add v3 support behind feature flag
joe-ayoub-segment Aug 19, 2026
c92eaa3
refactor(reddit-conversions-api): drop the v2 file split, restore uti…
joe-ayoub-segment Aug 19, 2026
7baa5e4
Move types-v3/utils-v3 into v3 folder, restore test_mode setting
joe-ayoub-segment Aug 19, 2026
489bc41
Centralize v2/v3 version identifiers into versioning-info.ts
joe-ayoub-segment Aug 19, 2026
34257f7
Extract standardEvent action definition into a reusable factory
joe-ayoub-segment Aug 19, 2026
55fa136
Reframe V2 as standard/V3 as beta; restore V2-pinned presets
joe-ayoub-segment Aug 19, 2026
b89918e
Migrate customEvent onto action.ts; drop Flagon flag gating
joe-ayoub-segment Aug 19, 2026
cc9f34b
Add per-item batch validation via MultiStatusResponse for V3; lock do…
joe-ayoub-segment Aug 19, 2026
61c98d7
Restructure createRedditPayloadV3 to mirror the legacy whole-batch flow
joe-ayoub-segment Aug 19, 2026
dde0afc
Dedupe EVENT_TYPE_V3 into TRACKING_TYPE_V3
joe-ayoub-segment Aug 19, 2026
c329461
Dedupe ACTION_SOURCE_V3 into ACTION_SOURCE_V3_LABELS
joe-ayoub-segment Aug 19, 2026
b2a796a
Revert test files to main
joe-ayoub-segment Aug 19, 2026
b3d8999
updating snapshots
joe-ayoub-segment Aug 19, 2026
3e68c88
Add unit tests for Reddit CAPI v3 single-event action and utils-v3 he…
joe-ayoub-segment Aug 19, 2026
d2a97d8
Add batch unit tests for Reddit CAPI v3
joe-ayoub-segment Aug 19, 2026
3e6f685
updating metadata
joe-ayoub-segment Aug 19, 2026
1c1221b
Merge branch 'main' into STRATCONN-6892-reddit-conversions-api-v3
joe-ayoub-segment Aug 19, 2026
989e578
test fixes
joe-ayoub-segment Aug 19, 2026
841ee98
fixing event_metadata bug
joe-ayoub-segment Aug 20, 2026
e563895
Merge branch 'main' into STRATCONN-6892-reddit-conversions-api-v3
joe-ayoub-segment Aug 20, 2026
41b3344
fixing pre-existing mapping bug
joe-ayoub-segment Aug 20, 2026
1dfd6b1
STRATCONN-6892: filter event_metadata by tracking_type per Reddit CAP…
joe-ayoub-segment Aug 21, 2026
474e893
Merge branch 'main' into STRATCONN-6892-reddit-conversions-api-v3
joe-ayoub-segment Aug 21, 2026
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
Expand Up @@ -2,51 +2,57 @@

exports[`Testing snapshot for actions-reddit-conversions-api destination: customEvent action - all fields 1`] = `
Object {
"events": Array [
Object {
"click_id": "Q1Q49h4LP@",
"event_at": "2021-02-01T00:00:00.000Z",
"event_metadata": Object {
"conversion_id": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"currency": "HNL",
"item_count": -2840640572358656,
"products": Array [
Object {
"category": "Q1Q49h4LP@",
"id": "Q1Q49h4LP@",
"name": "Q1Q49h4LP@",
},
],
"value_decimal": -28406405723586.56,
},
"event_type": Object {
"custom_event_name": "Q1Q49h4LP@",
"tracking_type": "Custom",
},
"user": Object {
"aaid": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"data_processing_options": Object {
"country": "GM",
"modes": Array [
"LDU",
"data": Object {
"events": Array [
Object {
"action_source": "APP",
"click_id": "Q1Q49h4LP@",
"event_at": 1612137600000,
"event_metadata": Object {
"conversion_id": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"currency": "HNL",
"item_count": -2840640572358656,
"products": Array [
Object {
"category": "Q1Q49h4LP@",
"id": "Q1Q49h4LP@",
"item_price": -28406405723586.56,
"name": "Q1Q49h4LP@",
"quantity": -2840640572358656,
},
],
"region": "Q1Q49h4LP@",
"value": -28406405723586.56,
},
"email": "8614950525b98787b31febe07634987a801175f098b60c83bd3903bff1ec3eed",
"external_id": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"ip_address": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"phone_number": "781a379f97f79178f343a118a245e179b55a414b1b959ef25376dcb43bfe61f6",
"screen_dimensions": Object {
"height": -2840640572358656,
"width": -2840640572358656,
"event_source_url": "Q1Q49h4LP@",
"type": Object {
"custom_event_name": "Q1Q49h4LP@",
"tracking_type": "CUSTOM",
},
"user": Object {
"aaid": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"data_processing_options": Object {
"country": "GM",
"modes": Array [
"LDU",
],
"region": "Q1Q49h4LP@",
},
"email": "8614950525b98787b31febe07634987a801175f098b60c83bd3903bff1ec3eed",
"external_id": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"ip_address": "4fbe858b31ee34144cc3d009fd4636ef20fb342e095570d49a9898bb8d77598e",
"phone_number": "781a379f97f79178f343a118a245e179b55a414b1b959ef25376dcb43bfe61f6",
"screen_dimensions": Object {
"height": -2840640572358656,
"width": -2840640572358656,
},
"user_agent": "Q1Q49h4LP@",
"uuid": "Q1Q49h4LP@",
},
"user_agent": "Q1Q49h4LP@",
"uuid": "Q1Q49h4LP@",
},
},
],
"partner": "SEGMENT",
"test_mode": true,
],
"partner": "SEGMENT",
"test_id": "Q1Q49h4LP@",
},
}
`;

Expand Down Expand Up @@ -84,7 +90,9 @@ Object {
Object {
"category": "gT6gXh8ga!%xhI",
"id": "gT6gXh8ga!%xhI",
"item_price": 20910002898206.72,
"name": "gT6gXh8ga!%xhI",
"quantity": 2091000289820672,
},
],
"value_decimal": 20910002898206.72,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import nock from 'nock'
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import Definition from '../index'
import { Settings } from '../generated-types'

const testDestination = createTestIntegration(Definition)
const timestamp = '2024-01-08T13:52:50.212Z'
const settings: Settings = {
ad_account_id: 'ad_account_id_1',
conversion_token: 'conversion_token_1'
}

describe('Reddit Conversions Api - V3 single event', () => {
it('should send a Purchase Standard event to v3 when api_version is v3 and action_source is set', async () => {
const event = createTestEvent({
timestamp: timestamp,
event: 'Order Completed',
messageId: 'test-message-id-contact',
type: 'track',
userId: 'user_id_1',
properties: {
click_id: 'click_id_1',
currency: 'USD',
quantity: 10,
revenue: 100,
uuid: 'uuid_1',
products: [{ product_id: 'product_id_1', category: 'category_1', name: 'name_1', quantity: 2, price: 25 }],
email: 'test@test.com'
},
context: {
userAgent: 'test-user-agent',
ip: '111.111.111.111',
page: { url: 'https://example.com/checkout' }
}
})

nock('https://ads-api.reddit.com').post('/api/v3/pixels/ad_account_id_1/conversion_events').reply(200, {})
const responses = await testDestination.testAction('standardEvent', {
event,
settings: { ...settings, test_id: 'test-123' },
useDefaultMappings: true,
mapping: {
tracking_type: 'Purchase',
api_version: 'v3',
action_source: 'WEBSITE'
}
})

expect(responses.length).toBe(1)
expect(responses[0].status).toBe(200)
expect(responses[0].options.json).toMatchObject({
data: {
partner: 'SEGMENT',
test_id: 'test-123',
events: [
{
action_source: 'WEBSITE',
event_source_url: 'https://example.com/checkout',
click_id: 'click_id_1',
event_at: 1704721970212,
type: {
tracking_type: 'PURCHASE'
},
event_metadata: {
currency: 'USD',
item_count: 10,
value: 100,
products: [
{
category: 'category_1',
id: 'product_id_1',
name: 'name_1',
quantity: 2,
item_price: 25
}
]
}
}
]
}
})
})

it('should route a Custom event to v3 with UPPER_SNAKE_CASE tracking_type', async () => {
const event = createTestEvent({
timestamp: timestamp,
event: 'Some Custom Event Name',
messageId: 'test-message-id-contact',
type: 'track',
userId: 'user_id_1',
properties: {}
})

nock('https://ads-api.reddit.com').post('/api/v3/pixels/ad_account_id_1/conversion_events').reply(200, {})
const responses = await testDestination.testAction('customEvent', {
event,
settings,
useDefaultMappings: true,
mapping: {
custom_event_name: 'Some Custom Event Name',
api_version: 'v3',
action_source: 'APP'
}
})

expect(responses.length).toBe(1)
const body = responses[0].options.json as { data: { events: Array<{ type: { tracking_type: string } }> } }
expect(body.data.events[0].type.tracking_type).toBe('CUSTOM')
})

it('should stay on v2 when api_version is not set (existing customers)', async () => {
const event = createTestEvent({
timestamp: timestamp,
event: 'Order Completed',
messageId: 'test-message-id-contact',
type: 'track',
userId: 'user_id_1',
properties: { revenue: 100 }
})

nock('https://ads-api.reddit.com').post('/api/v2.0/conversions/events/ad_account_id_1').reply(200, {})
// api_version is genuinely absent from the mapping here - that's the real "existing customer,
// pre-dates this field" shape. A literal '' would fail the field's enum (['v3', 'v2.0'])
// validation before perform() ever runs, so it can't be used to exercise this scenario.
const responses = await testDestination.testAction('standardEvent', {
event,
settings,
useDefaultMappings: true,
mapping: {
tracking_type: 'Purchase'
}
})

expect(responses.length).toBe(1)
expect(responses[0].status).toBe(200)
})

it('should reject the mapping when api_version is v3 but action_source is not set', async () => {
const event = createTestEvent({
timestamp: timestamp,
event: 'Order Completed',
messageId: 'test-message-id-contact',
type: 'track',
userId: 'user_id_1',
properties: { revenue: 100 }
})

await expect(
testDestination.testAction('standardEvent', {
event,
settings,
useDefaultMappings: true,
mapping: {
tracking_type: 'Purchase',
api_version: 'v3'
}
})
).rejects.toThrow("The root value is missing the required field 'action_source'.")
})

it('should throw when a product is missing an id (fails Destination-side validation, not schema validation)', async () => {
const event = createTestEvent({
timestamp: timestamp,
event: 'Order Completed',
messageId: 'test-message-id-contact',
type: 'track',
userId: 'user_id_1',
properties: {
revenue: 100,
products: [{ category: 'category_1', name: 'name_1' }]
}
})

await expect(
testDestination.testAction('standardEvent', {
event,
settings,
useDefaultMappings: true,
mapping: {
tracking_type: 'Purchase',
api_version: 'v3',
action_source: 'WEBSITE'
}
})
).rejects.toThrow('products.id is required when sending to Reddit Conversions API v3')
})
})
Loading
Loading