Meta event_id deduplication across Pixel, App SDK, and CAPI
Updated July 10, 2026 · 10 min read
Meta deduplication is not a generic UUID problem. It is a distributed event identity problem across at least two producers: a client producer that sees browser or device identity, and a server producer that sees verified money.
If the two producers do not share the same event_name and event_id for the same economic fact, Meta cannot collapse them into one conversion. The result is either inflated revenue, discarded server signal, or a misleadingly healthy Events Manager view with weak optimization input.
The unit of deduplication
The deduplication unit is the conversion fact, not the network request. A single Stripe invoice, App Store transaction, Play Billing purchase token, RevenueCat event, or Adapty transaction should have one durable event_id that every producer reuses.
The client event contributes high quality identity: _fbp, _fbc, app scoped identifiers, device context, and session timing. The server event contributes authority: verified payment state, final value, currency, and transaction ID. Deduplication lets Meta consume both surfaces while counting one conversion.
Event identity contract
Do not let each producer call crypto.randomUUID independently. Generate or derive an event_id from the transaction boundary and pass it through metadata.
type DedupedConversion = {
event_name: "Purchase" | "Subscribe" | "StartTrial";
event_id: string; // stable across client and server
transaction_id: string; // Stripe session, invoice, app store transaction, etc.
value: number;
currency: string;
source: "pixel" | "app_sdk" | "capi";
};
const eventId = `purchase:${checkoutId}`;Failure modes that look harmless in dashboards
- Browser Pixel sends Purchase with eventID A; Stripe webhook sends CAPI Purchase with event_id B. Meta sees two purchases.
- Mobile SDK logs Purchase on receipt callback; RevenueCat webhook sends Purchase later without the SDK event ID. The verified server event cannot be joined.
- The browser path sends Subscribe while the server path sends Purchase for the same transaction. Same event_id is not enough if event_name differs.
- The server retry path regenerates event_id after a timeout. One real payment produces multiple CAPI events.
- The client sends value before discounts or tax; the server sends net paid value. Dedup may collapse the event, but value optimization sees inconsistent economics.
Repair protocol
- 1Choose the transaction boundary that creates billing intent: checkout session, purchase token, app store transaction, or subscription platform event.
- 2Create event_id at that boundary and persist it in order metadata, checkout metadata, subscriber attributes, or your own purchase row.
- 3Emit the client event opportunistically with the same event_id for identity capture.
- 4Emit the CAPI event from the verified payment source with the same event_name, event_id, value, currency, and transaction_id.
- 5Make webhook retries idempotent so retrying the transport does not create a new Meta event identity.
- 6Validate in Test Events that one deduplicated conversion has both client and server surfaces rather than two independent conversions.
Bad pattern: two independent IDs
// Browser
fbq("track", "Purchase", purchaseData, { eventID: crypto.randomUUID() });
// Webhook
await sendCapi({
event_name: "Purchase",
event_id: crypto.randomUUID(),
custom_data: purchaseData,
});Good pattern: one conversion ID
const eventId = order.meta_event_id;
fbq("track", "Purchase", browserPurchaseData, { eventID: eventId });
await sendCapi({
event_name: "Purchase",
event_id: eventId,
custom_data: verifiedPurchaseData,
});Frequently asked questions
Why is Meta counting my purchases twice?
Because the client and server events are not sharing the same event_name and event_id for the same transaction. Generate or persist one event_id per conversion and reuse it across Pixel, App SDK, and CAPI.
What is event_id in Meta deduplication?
It is the conversion identity you assign to one economic event, such as a checkout payment or app store transaction. Meta uses it together with event_name to decide whether client and server events are duplicates.
Should event_id be generated on the client or server?
It should be generated at the conversion boundary and persisted where both sides can read it. In web checkout that is usually checkout creation. In mobile subscriptions it may be purchase metadata, subscriber attributes, or your own transaction row.
Do the client and server events have to arrive at the same time?
No. They can arrive at different times. They must, however, carry the same event_name and event_id and describe the same conversion fact.
Related guides
Not sure which signal path is breaking?
Zoruko audits your web and mobile code for broken Meta and TikTok signal chains, then ships each fix as a pull request you review.