Cross-Platform Deduplication
When Signal runs alongside your existing vendor pixels (Meta Pixel, TikTok Pixel, Pinterest Tag, etc.), each conversion can reach the vendor twice — once from the browser pixel, once from Signal’s server-side delivery. Without coordination, the vendor counts the same conversion twice.
The fix is a shared identifier per event: pass the same value as event_id on Signal’s track() call and as the vendor pixel’s eventID parameter. The vendor matches the two events within their dedup window (usually ~48 hours) and counts it once.
This pattern is the foundation for dual-tagging deployments: keep the existing pixels live, deploy Signal alongside, and let the vendor dedup based on shared event IDs. Once you’re confident Signal is delivering correctly, you can remove the pixels and Signal continues to deliver server-side without disruption.
The pattern
Pick a stable identifier from your backend (order ID, lead ID, subscription ID — anything unique to the conversion). Pass it on both sides:
Signal (datafly.js)
datafly.track('purchase', {
event_id: order.id,
transaction_id: order.id,
value: 99.99,
currency: 'GBP',
});The corresponding pixel call
// Meta Pixel
fbq('track', 'Purchase',
{ value: 99.99, currency: 'GBP' },
{ eventID: order.id }
);
// TikTok Pixel
ttq.track('PlaceAnOrder', {
value: 99.99,
currency: 'GBP',
event_id: order.id,
});
// Pinterest Tag
pintrk('track', 'checkout', {
value: 99.99,
currency: 'GBP',
event_id: order.id,
});That’s it. As long as both sides see the same value within the vendor’s dedup window, the conversion counts once.
Vendor matrix
| Vendor | Pixel parameter | Server field | Dedup window |
|---|---|---|---|
| Meta CAPI | eventID (3rd arg to fbq) | event_id | 48 hours |
| TikTok Events API | event_id in pixel params | event_id | 48 hours |
| Pinterest Conversions API | event_id in pixel params | event_id | 24–48 hours |
| Amplitude HTTP V2 | insert_id | event_id (mapped to insert_id) | 7 days |
| WP Ads | event_id in pixel params | event_id | 24 hours |
| Google Ads (Enhanced Conversions) | transaction_id (gtag) | order_id (CAPI) | per-conversion |
| Google DV360 / CM360 | n/a (uses gclid for match) | n/a | n/a |
| GA4 Measurement Protocol | transaction_id for purchase events | transaction_id | per-event |
Google Ads and DV360 don’t dedup by an arbitrary event_id — they dedup by Google Click ID (gclid) plus transaction_id. If you’re running Enhanced Conversions alongside Signal, set transaction_id to your stable order ID on both gtag and Signal’s track() call.
How Signal resolves the event_id
When a track() call includes event_id in its properties, Signal’s SDK promotes it to the top-level eventId envelope field and removes it from properties so it isn’t double-mapped. Inside the pipeline, the source path event_id resolves to the envelope eventId with messageId as the fallback when the customer didn’t supply one.
Crucially, all placement into the vendor payload is driven by the blueprint — there is no hidden auto-injection in the delivery layer. Each vendor’s default blueprint includes an explicit mapping that routes the resolved event_id to the field name the vendor’s API expects:
| Vendor | Mapping in default blueprint |
|---|---|
| Meta CAPI | source: event_id → target: event_id |
| TikTok Events API | source: event_id → target: event_id |
| Pinterest Conversions API | source: event_id → target: event_id |
| WP Ads | source: event_id → target: event_id |
| Amplitude HTTP V2 | source: event_id → target: insert_id |
If you customise a blueprint or strip the mapping, the field is not sent — there’s no silent fallback. Open the blueprint Mapping tab to see exactly which fields are wired to event_id.
Amplitude: dedup also requires matching device_id
Amplitude dedupes on device_id + insert_id within a 7-day window. The shared insert_id (covered above) is necessary but not sufficient — device_id must also match between the browser Amplitude SDK and Signal’s server-side delivery.
By default they diverge:
- Amplitude SDK auto-generates a
device_id(UUID) and persists it in its own cookie. - Signal’s blueprint maps Datafly’s
anonymous_idtodevice_id.
Align them by setting Amplitude’s deviceId to Datafly’s anonymousId once both SDKs are loaded:
amplitude.init('YOUR_AMPLITUDE_API_KEY', {
deviceId: datafly.getAnonymousId(),
});Or, post-init:
amplitude.setDeviceId(datafly.getAnonymousId());For known users, pass the same user_id to both:
datafly.identify('user-123', { email: '...' });
amplitude.setUserId('user-123');If device_id differs between the pixel and server events, Amplitude treats them as separate users and dedup will not apply — even if insert_id matches.
Testing dedup
After deploying both pixel and Signal:
- Trigger a test conversion from a real browser session.
- Open the vendor’s test events tool:
- Meta: Events Manager → Test Events tab
- TikTok: Events Manager → Test Events
- Pinterest: Conversion Tags → Test Events
- Confirm the event appears with both Browser and Server sources, marked as deduplicated.
- If the event appears twice (no dedup badge), check that:
- Both calls use exactly the same
event_id(string equality, no whitespace, same case) - Both events are within the dedup window (don’t test with one fired hours after the other)
- The pixel
eventNameand Signal’s vendor event name match
- Both calls use exactly the same
Why this matters
Without dedup, vendor reports inflate revenue by the share of double-counted events. For a typical e-commerce setup with one pixel + one CAPI integration, that’s a 100% inflation on the conversions that fire on both sides. Multiplied across 6–8 vendor pixels, total reported revenue can hit 150–220% of actual revenue.
Sharing event_id across pixel and CAPI restores accuracy without requiring you to remove the pixel — the foundation for moving to a Signal-led attribution model where the pixels are eventually removed entirely.