Deduplication
When a single user converts, you're often sending the same conversion event to Snap through more than one integration — for example, the Snap Pixel SDK firing in the browser alongside a server-to-server Conversions API (CAPI) call, or a Mobile Measurement Partner (MMP) postback alongside direct CAPI for an app. Without deduplication signals, that same event can be counted more than once in your reporting and your optimization signal can be weaker than it should be.
This page describes how to set up deduplication on your side so that Snap can recognize redundant submissions as the same conversion. The first half describes the core principles which apply whether you're running web or app. The Implementation section at the bottom then splits into web-specific and app-specific guidance — pick the platform tab once and the entire implementation walkthrough will be tailored to your setup.
Core dedup principles
These apply regardless of which integrations you're sending events through.
Send the same unique identifier across each integration
For Snap to recognize the same conversion arriving via more than one source, each event needs to carry the same unique identifier on the corresponding parameter, for the same Snap asset (Pixel ID for web, Snap App ID for app).
When the same identifier is present on both events for the same asset, the two are treated as a single conversion in reporting and optimization rather than two separate ones. Beyond that match, Snap performs additional signal-quality evaluation behind the scenes to choose the strongest representation of the event — so the cleaner the dedup signals you send, the better that evaluation works on your behalf.
One id per conversion event
The id must be unique to that single conversion event — not per session, per visit, or per user. For example, a user who views three product pages and adds two items to their cart in one visit generates five distinct conversion events; each needs its own unique id. Re-using one value across multiple distinct conversions (e.g. passing the same session id on every PAGE_VIEW and ADD_CART in a session) will cause those events to be collapsed together in reporting, leading to under-counting.
Good sources for this id, in order of preference:
- An existing transaction id or order id (if your conversion has one).
- An existing lead id (for lead-form conversions).
- A freshly generated
UUIDv4.
If your system already produces one, recycle it across your integrations — you don't need to invent a new identifier.
Send real ids, not placeholders
Make sure the id you send is a real, unique identifier — not a templating token or placeholder. Common mistakes to watch out for include unsubstituted template strings (e.g. {{order.order_number}}, %OrderId%), generic literals like null, undefined, or NA, and raw JavaScript expressions that leaked through from your tag manager configuration. Placeholder values may not be honored for deduplication.
If you see your conversion counts looking inflated in reporting, check the dedup diagnostics in Events Manager to confirm the values you're sending are being recognized. Once you fix the templating on your end, allow up to 24 hours for the change to be reflected before re-checking.
Dedup windows
How long after the first event arrives can a second event still be matched to it? The window depends on which identifier pair the dedup is keyed on:
| Identifier pair | Dedup window |
|---|---|
Cross-channel dedup id (e.g. Pixel SDK client_dedup_id ⇄ CAPI event_id for web) | 48 hours |
transaction_id ⇄ order_id — PURCHASE events | up to 30 days |
A couple of practical implications:
- If your events are sent within minutes of each other (the usual case), the dedup windows are easily met and you have nothing to worry about.
- The 30-day window on
PURCHASEtransaction_id/order_idis intentionally longer so that late-arriving offline confirmations (e.g. server-side purchase events that fire well after the on-site Pixel event) can still be matched. See Purchase event recommendations below.
Purchase event recommendations
For PURCHASE events we recommend sending the order or transaction reference in addition to the cross-channel dedup id. This gives Snap two independent signals to recognize the same purchase across integrations, and adds a longer (30-day) safety net for late-arriving confirmations.
When both pairs are present, the cross-channel dedup id pair takes precedence, with the 48-hour window. If those identifiers don't match between the two events, the transaction / order id pair acts as a fallback, with a longer window suited to late-arriving purchase confirmations.
In practice: if you can only ship one of the two pairs, ship the cross-channel dedup id for everyday correctness; ship the transaction / order id pair as well to add a generous safety net specifically for purchases.
The Pixel SDK accepts this value under the parameter name transaction_id. CAPI v3 accepts the same value under the parameter name order_id (inside custom_data). They are not separate fields — they are the same field with different names on either side of the integration.
For payload examples that show all of this in action, see the platform-specific Implementation section below.
Implementation
Pick your platform below — the setup steps, examples, and platform-specific notes are all in the tab you choose.
- Web
- App
For web, the cross-channel match is between Pixel SDK client_dedup_id and CAPI event_id for the same Pixel ID.
What should agree. For the Pixel SDK event and CAPI event to be recognized as the same conversion:
- The pixel — the asset id in the CAPI endpoint URL should be the same Pixel ID that fired the Pixel SDK event.
- The event name —
event_namein CAPI (e.g.SIGN_UP) should match the event name passed tosnaptr('track', ...). - The identifier —
client_dedup_idin Pixel SDK andevent_idin CAPI should be identical. Treat these values as opaque strings: send the same value on both sides, with the same casing, whitespace, and characters.
Setup steps. The example below uses Google Tag Manager (GTM), but the same pattern applies to any tag manager or hand-rolled tagging.
1. Expose your unique id to your tag manager. In GTM, push the value into the dataLayer when the conversion happens, and read it back via a Data Layer Variable or Custom JavaScript Variable.
2. Inject the id into the Pixel SDK tag as client_dedup_id on the snaptr('track', ...) call:
snaptr('track', 'SIGN_UP', {
client_dedup_id: '{{ dlv - dedup_id }}',
user_email: '{{ dlv - user_email }}',
});
If you don't pass client_dedup_id explicitly, the Pixel SDK will auto-generate one client-side (prefixed with @-). That auto-generated id is only known to the browser — it cannot match anything your server sends to CAPI. You must set client_dedup_id explicitly for cross-channel deduplication to work.
3. Pass the same id as event_id in your CAPI payload. Whichever service fires your CAPI event needs the same id:
{
"data": [
{
"event_name": "SIGN_UP",
"event_time": 1746048000,
"event_source_url": "https://example.com/signup",
"action_source": "WEB",
"event_id": "txn_abc123",
"user_data": { "em": "..." }
}
]
}
See the full list of fields on the Parameters page.
Several Pixel SDK parameters have differently-named CAPI v3 counterparts. Be sure you're writing the value into the right field on each side:
| Concept | Pixel SDK parameter | CAPI v3 parameter |
|---|---|---|
| Cross-channel dedup id | client_dedup_id | event_id (top-level) |
| Order / transaction reference | transaction_id | order_id (inside custom_data) |
| Category for custom conversions | item_category | content_category (inside custom_data) |
If you're migrating from CAPI v2 you may see transaction_id referenced on the CAPI side too — that's the legacy v2 name. In CAPI v3 the field is order_id; see the Migration Guide for the full v2-to-v3 rename list.
PURCHASE example. Set all four parameters across the two integrations. CAPI payload — order/transaction reference goes under order_id inside custom_data:
{
"event_name": "PURCHASE",
"event_id": "1001-2025-05",
"event_time": 1746048000,
"event_source_url": "https://example.com/checkout/complete",
"action_source": "WEB",
"user_data": { "em": "..." },
"custom_data": {
"value": 89.99,
"currency": "USD",
"order_id": "1001-2025-05"
}
}
Pixel SDK call — same value goes under transaction_id:
snaptr('track', 'PURCHASE', {
client_dedup_id: '1001-2025-05',
transaction_id: '1001-2025-05',
price: '89.99',
currency: 'USD',
});
Notice how 1001-2025-05 appears four times across the two integrations — twice on each side, once for each of the two recommended parameter pairs.
App conversions typically reach Snap through a Mobile Measurement Partner (MMP) or via direct CAPI with action_source: MOBILE_APP. Because the upstream signal sources are different, the dedup mechanics are distinct from the web flow, but the same principle applies: send a consistent identifier across each integration, targeting the same Snap App ID. The advertiser-controllable identifier on the app side is transaction_id (on the MMP) and the matching order_id inside custom_data (on direct CAPI).
What should agree.
| Source | Field to set |
|---|---|
| MMP postback | Configure your MMP to forward your unique id as transaction_id (per your MMP's documentation). Applies to all events. |
Direct CAPI (MOBILE_APP) | Send the same id as order_id inside custom_data. |
Use the same value across both sources, and make sure both target the same Snap App ID.
Setup steps.
1. Configure your MMP to forward your unique id as transaction_id. Most MMPs let you choose which field in your in-app event maps to Snap's transaction_id. Your MMP's documentation will describe the exact mapping.
2. Pass the same id as order_id inside custom_data on your direct CAPI payload with action_source: MOBILE_APP. The required app_data block should include your app_id (numeric for iOS, package name for Android) and a valid extinfo array — see the App Data Parameters on the Parameters page.
{
"data": [
{
"event_name": "PURCHASE",
"event_time": 1746048000,
"action_source": "MOBILE_APP",
"user_data": { "em": "..." },
"app_data": {
"advertiser_tracking_enabled": 1,
"extinfo": ["a2", "com.example.app", "1.0", "1.0", "13.0", "..."],
"app_id": "com.example.app"
},
"custom_data": {
"value": 89.99,
"currency": "USD",
"order_id": "txn_abc123"
}
}
]
}
App-specific notes.
- The
transaction_id⇄order_idcross-source pair is most actively used onPURCHASEevents today, where it gives the 30-day dedup window described above. For non-PURCHASEapp events, deduplication today is anchored on device identifiers (hashed IDFA / IDFV / Android App Set ID) plusapp_idrather than on this pair — but sending the pair on those events is still a good practice. - Apple SKAdNetwork postbacks are handled internally on Snap's side — there is nothing you need to set there for deduplication; we key it off the platform-provided identifiers.
Additional reading for apps. For the broader app attribution flow (Snap App ID setup, MMP integration, postbacks):
For an in-depth walkthrough specific to your MMP, reach out to your Snap account team.
Verifying your setup
After you ship the changes, allow up to 24 hours for traffic to flow through and for the diagnostics to update, then:
- In Events Manager, open your Pixel (for web) or Snap App (for app).
- Navigate to the event in question (e.g.
SIGN_UPorPURCHASE) and open Diagnostics. - Confirm:
- Web: the cross-channel dedup id is present on each integration (Pixel SDK
client_dedup_idand CAPIevent_id). ForPURCHASE, also confirmtransaction_id(Pixel SDK) andorder_id(CAPI). - App: for
PURCHASE, thetransaction_id(forwarded by your MMP) andorder_id(on direct CAPI insidecustom_data) are both present and carry the same value. - The dedup-related diagnostics for the event look healthy.
- Web: the cross-channel dedup id is present on each integration (Pixel SDK
Anyone on your team with access to Events Manager can self-serve these checks at any time.
You can also use Snap's Request Check Tool and the test event endpoints to validate your payloads before deploying to production.
Common pitfalls
- Different ids across integrations. A common mistake is to generate a fresh UUID on the server when firing the CAPI event, rather than re-using the same id that was sent on the other integration. They must be the same value, not two independently-minted ids.
- Mismatched
event_name. SendingSIGN_UPfrom one integration andsignup(orSIGNUP) from another will prevent matching. Use the canonical event names listed in Parameters. - Mismatched asset id. The Pixel ID (or Snap App ID) used on one integration must be the same asset id used by the other.
- Sending the order/transaction id under the wrong CAPI field. Common when reading the Pixel SDK docs and the CAPI docs side by side: the Pixel SDK calls this field
transaction_id, but CAPI v3 calls itorder_id(insidecustom_data). They hold the same value but have different parameter names. Sendingtransaction_idat the top level of a CAPI v3 payload won't have the intended effect. - Placeholder order/transaction values. A
transaction_id(ororder_id) of{{order.order_number}}is no id at all — make sure your templating is being substituted before the event fires. - Re-using one id across many conversions. Each conversion needs a unique id. Re-using the same
event_idvalue across many real conversions can cause them to be collapsed together in reporting. - (Web only) Auto-generated Pixel SDK
cdid. If you don't passclient_dedup_idexplicitly, the Pixel SDK will mint a UUID prefixed with@-. That id is local to the browser session and cannot match what your server sends to CAPI. Always setclient_dedup_idyourself.
Further reading
- Conversions API Parameters — full schema including
event_id,order_id, andcontent_category. - Best Practices — short version of this guidance plus general data-hygiene rules.
- Verify Setup — test event endpoints and the Request Check Tool.
- Snap Business Help Center: