Skip to main content
GUIDE · DEDUP11 min read

Dedup between client pixels and server-side CAPI: how it actually works

Browser pixels and server-side conversion APIs are strongest when they run together. Deduplication is the contract that lets both paths report the same customer action without turning one purchase into two reported conversions.

Purpose

Why dedup exists

Modern ad measurement often sends the same conversion twice on purpose. The browser Pixel fires close to the session, where it can see cookies, click IDs, page context, and immediate user behavior. The server-side CAPI event fires from your backend, checkout, tag gateway, or tracking layer, where it can include authoritative order data, normalized customer identifiers, and a more reliable delivery path.

Platforms do not want those two paths to double-count one action. If a shopper completes one checkout and both Pixel and CAPI send Purchase, Meta, TikTok, Pinterest, Snapchat, Reddit, and Google Ads need a way to collapse the pair into one conversion. Dedup is that merge rule. It lets you keep browser speed and server durability while preserving a single reporting outcome.

Contract

The dedup contract

Every destination has its own vocabulary, but the pattern is consistent: send a stable primary key, include browser or click context when the platform expects it, and deliver the matching events within the platform's dedup window.

PlatformPrimary keySecondary keysWindow
Metaevent_id + fbpemail_hash OR phone_hash48h
Google Adsgclid OR order_id72h
TikTokevent_id + (email OR phone OR external_id)48h
Pinterestevent_id + epik48h
Snapchatevent_id + sc_click_id7d
Redditevent_id + rdt_uuid7d
Example

The Meta dedup example

Meta's browser Pixel uses eventID in the fourth argument of fbq. Meta CAPI uses event_id in the server payload. Those names differ, but the value must be identical. If the browser sends evt_xyz and the server sends evt_xyz for the same Purchase, Meta has the core key it needs to collapse the pair.

// Client-side Pixel
fbq('track', 'Purchase', {}, { eventID: 'evt_xyz' });
// Server-side CAPI payload
{
  "event_name": "Purchase",
  "event_time": 1776943226,
  "event_id": "evt_xyz",
  "action_source": "website",
  "user_data": {
    "fbp": "fb.1.1776942000000.1234567890",
    "em": ["6b3a55e0261b..."]
  },
  "custom_data": {
    "currency": "EUR",
    "value": 129.95
  }
}

Meta's logic is practical rather than mystical. If both event_ids match, and the timing and browser context are plausible, Meta collapses the Pixel and CAPI pair into one conversion. If only Pixel fires, there is no CAPI de-dup because there is no server event to merge. If only CAPI fires, there is no Pixel de-dup because there is no browser event to merge.

The fbp cookie matters because it ties the server event back to the browser context. event_id says which two events claim to be the same action; fbp helps show that the server payload belongs to the same browser that produced the Pixel event.

IDs

The event_id convention

A good event_id is generated once at the moment the conversion action is created. It should be unique per conversion, shared by every transport, and persistent for retries. For a checkout, that usually means creating the ID when the order is confirmed or when the conversion envelope is assembled, then passing it to the browser Pixel call and the server dispatch.

Avoid IDs that are too broad. A customer ID is not a purchase ID. A cart ID can be reused after recovery flows. A product ID is shared by many shoppers. Better patterns include a UUID stored with the event, or a deterministic value such as order_98371_purchase when the order system guarantees uniqueness for that event type.

Persistence matters most during failures. If the CAPI endpoint rejects a payload because of rate limits, network loss, or a temporary destination outage, retry the same event with the same event_id. A retry is delivery repair, not a new conversion.

Failures

When dedup breaks

Dedup failures usually come from contract drift: one side changes the ID, one side loses browser context, timestamps fall outside the expected range, or retry logic accidentally turns a single conversion into several unique events.

01

Pixel blocked before event_id generation

If the ID is generated only inside the Pixel path, ad blockers can prevent both the Pixel event and the ID from existing. The server event then arrives without the shared key the platform expected.

02

CAPI retry lands after the window

A failed server event can be worth retrying, but retrying after Meta, TikTok, or Pinterest's 48-hour window means the destination may treat it as a late standalone event.

03

Browser and server use different event_id values

This is the classic double-counting bug. The Pixel reports Purchase with evt_a, the server reports Purchase with evt_b, and the platform cannot know they represent the same checkout.

04

Timestamp drift makes the pair look unrelated

Timezone bugs, milliseconds sent where seconds are expected, or queue delays can make two matching IDs appear outside the reasonable event_time range for a single action.

05

The fbp or click cookie is lost on checkout

Redirect checkouts, payment providers, and subdomain changes often drop browser identifiers. The server event may keep the right event_id but lose the browser context that improves confidence.

06

Event names do not match

Purchase on the browser and CompletePayment on the server are different events to most destination logic. A shared ID does not help if the semantic event contract changed.

07

Retries create fresh IDs

A retry should resend the same conversion under the same event_id. If every retry generates a new ID, the platform sees multiple unique attempts instead of one durable event.

08

Order IDs are reused across partial payments

Installments, split tenders, and replacement orders can reuse human-facing order references. If that field doubles as the dedup key, legitimate conversions can be collapsed incorrectly.

TrackLayer

TrackLayer's dedup insurance

TrackLayer treats dedup as a pipeline invariant. The event identity is created and preserved before a destination-specific payload exists, so browser and server dispatches are different views of the same conversion rather than separate guesses.

event_id is auto-generated

TrackLayer creates the event identifier at the shared event envelope layer, before destination dispatch, so the identifier is not dependent on a single browser tag completing successfully.

fbp is captured server-side

The _fbp cookie is read from eligible incoming requests and attached to the server event context. That keeps Meta dedup and match quality from depending on late checkout JavaScript alone.

Both sides receive the same context

Pixel calls and CAPI payloads are built from the same conversion envelope. The browser event gets eventID while the server event gets event_id, and both point back to the same action.

Rejected CAPI events retry under the same ID

Temporary destination errors do not create a new conversion identity. TrackLayer retries rejected server events with the original event_id so recovery does not become duplication.

Monitoring

Measuring dedup health

Dedup is not healthy just because conversion volume looks stable. You need metrics that prove the browser and server paths are sharing identity, reaching the platform inside the window, and being accepted without suspicious duplication warnings.

dedup_ratio

The share of server events that the platform reports as matched to a browser event. Watch it by platform, event_name, checkout surface, and consent state.

shared_event_pct

The percentage of eligible conversions where both Pixel and CAPI carried the same event_id. This catches wiring issues before destination diagnostics lag behind.

platform_dup_alerts

Destination warnings about duplicate events, unmatched server events, or suspicious event_id reuse. Treat them as deployment signals, not dashboard noise.

Debugging

Troubleshooting

Start with symptoms, then inspect raw payload pairs. Most production issues are visible when you compare the browser event, the server event, the final destination response, and the retry log for the same conversion.

Reported purchases jump after enabling CAPI

Pixel and server events are both firing, but event_id is missing or different between the two paths.

CAPI volume is healthy but dedup ratio is low

Server events are arriving without fbp, gclid, epik, or the destination's browser or click identifier.

Only paid traffic duplicates

Click ID capture is inconsistent on landing pages, especially when redirects strip query parameters before cookies are written.

Duplicates appear after retry spikes

The retry worker is generating new event IDs instead of preserving the original ID from the failed event.

Dedup works in testing but fails in production

The test flow uses one domain and one checkout path, while production uses additional subdomains, payment redirects, or delayed fulfillment events.

Legitimate second purchases disappear

The dedup key is too broad, often a cart ID or customer ID reused across multiple real purchases.

FAQ

FAQ

Should Pixel and CAPI both fire for the same purchase?

Yes, when consent and platform policy allow it. The browser event gives the destination immediate session context, and the server event gives a more durable conversion record. Dedup is what makes the pair count as one conversion.

Is event_id the same as order_id?

It can be derived from order_id, but it should represent a specific conversion event. For example, order_4921_purchase is safer than just 4921 when you also send refunds, subscriptions, or post-purchase events.

What happens if only Pixel fires?

There is nothing to deduplicate. The platform receives one browser event and may attribute it normally, but you lose the server-side backup and any stronger first-party identifiers that CAPI would have carried.

What happens if only CAPI fires?

Again, there is nothing to deduplicate. The server event can still be useful, especially with strong match keys, but it will not be collapsed against a missing browser event.

Can dedup hide real conversions?

Yes, if the key is reused across different actions. event_id should be unique per conversion, not per user, session, cart, or product. Reuse causes separate actions to look like duplicates.

Continue

Next reads

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.