Event Deduplication: Why Your Meta Conversions Count Double and How to Fix It
Meta Pixel and Conversions API send the same event twice. Without event_id matching, Meta counts duplicate purchases. Here is how correct deduplication works.
Key Takeaways
- Without deduplication, Meta counts 30 to 80% more conversions than actually occur
- event_id is the only reliable deduplication mechanism between Pixel and CAPI
- Meta deduplicates within a 48-hour window: identical event_id + event_name = one event
- Google and Meta handle deduplication differently: Google uses transaction_id, Meta uses event_id
Key Takeaways
- Without event_id, Meta inflates your purchase events by 40 to 80% and your ROAS looks better than it is
- Smart Bidding on Meta optimizes on false conversion numbers and allocates budget inefficiently
- Correct deduplication enables precise campaign performance analysis instead of phantom numbers
- Meta Events Manager shows deduplication status live for all events
Key Takeaways
- False conversion numbers lead to wrong decisions in budget allocation in the six-figure range
- Deduplication is not an optional optimization but mandatory for correct ROI calculation
- Implementation effort under 4 hours, then permanently correct Meta attribution
- Monitoring via Events Manager prevents gradual data quality deterioration
Key Takeaways
- event_id must be identical between fbq Pixel (eventID parameter) and CAPI (event_id field)
- Shopify Order ID as basis for Purchase events, Product ID + Session ID for ViewContent
- Meta deduplicates via event_name + event_id within 48h window, first event counts
- Casing critical: eventID (camelCase) in Pixel, event_id (snake_case) in CAPI JSON
Since 2023, Meta has recommended using the Conversions API (CAPI) in parallel with the browser Pixel. The reasoning is sound: ad blockers block the Pixel for 15 to 30% of users, Safari ITP limits cookie lifetimes, and the server-side CAPI delivers data regardless of browser restrictions. The combination of Pixel and CAPI maximizes data coverage.
But "maximizes data coverage" also means: every event is potentially sent twice. Once from the Pixel in the browser, once from the CAPI via the server. If Meta counts both events as separate conversions, your reports show 30 to 80% more purchases than actually occurred. Your ROAS looks fantastic, your budget planning is based on phantom numbers.
The solution is event deduplication. And it only works when browser and server send the same event with the same ID.
For you as a campaign manager: Without event_id deduplication, Meta counts 150 purchases whilst Shopify shows 100 actual orders. Your ROAS of 450% is actually 300% (50% inflation). Smart Bidding on Meta optimises on false conversion numbers and allocates budget inefficiently. You scale budgets by 30-50% based on phantom conversions. Correct deduplication via event_id enables precise campaign performance analysis and ROAS calculations that match reality.
For you as a decision-maker: Double conversion counting inflates ROAS from 300% to 500% (67% error). Channel that looks profitable at ROAS 5 is actually at ROAS 3. At 100,000 EUR monthly budget, this causes 20,000-40,000 EUR misallocation to wrong channels. Implementation: under 4 hours. Return: permanently correct Meta attribution and budget decisions based on reality instead of inflated phantom conversions.
For developers: Event deduplication requires that browser Pixel and server CAPI send exactly the same event_id for the same event. The ID must come from the DataLayer (transaction_id), not be generated per request. A UUID per request leads to different IDs and thus no deduplication.
How double-counting occurs
Three scenarios that appear in almost every setup:
Scenario 1: Pixel + CAPI without event_id. A user makes a purchase. The Pixel sends a Purchase event to Meta. The GTM Server Container sends the same Purchase event via CAPI to Meta. Meta receives two events but has no way to recognize that they represent the same purchase. Result: 2 conversions instead of 1.
Scenario 2: Pixel + CAPI with different event_id. The Pixel generates a random event_id. The server generates a different random event_id. Meta receives two events with different IDs and counts them as separate conversions. Result: identical to Scenario 1.
Scenario 3: Pixel blocked, CAPI active. An ad blocker blocks the Meta Pixel. Only the CAPI sends the Purchase event. Meta receives one event. Result: correct, 1 conversion. Deduplication is not needed in this case, but the event_id does no harm.
The problem only affects Scenarios 1 and 2. And these scenarios are the norm for users without ad blockers, which is 70 to 85% of your traffic.
Meta's deduplication mechanism
Meta deduplicates events based on two fields:
- event_name: The name of the event (e.g.
Purchase,AddToCart,ViewContent) - event_id: A unique ID that identifies the specific event instance
The rule: if Meta receives two events with identical event_name and identical event_id within 48 hours, the second event is recognized as a duplicate and discarded. Only the first one counts.
Requirements for event_id
The event_id must:
- Be unique per event instance. Every purchase needs its own ID. Two different purchases must not share the same ID.
- Be identical between Pixel and CAPI. Browser and server must send exactly the same ID for the same purchase.
- Be stable. No regeneration on page reload. If the user loads the thank-you page twice, the ID must remain the same.
What works as event_id
| Source | Example | Suitable? |
|---|---|---|
| Shopify Order ID | #1042 > purchase_1042 |
Yes, stable and unique |
| Transaction ID | 5312847 |
Yes, identical between browser and server |
| Random UUID (generated per client) | a3f7c2d-... |
Only if the exact same UUID goes to Pixel AND server |
| Random UUID (generated per request) | b8e1f4a-... (Pixel) vs c9d2e5b-... (Server) |
No, different IDs = no deduplication |
| Timestamp | 1711382400 |
No, Pixel and server have slightly different timestamps |
The safest option: the Shopify Order ID or Transaction ID as a basis. This exists in both contexts (browser DataLayer and server request) and is by definition unique per purchase.
Implementation in Shopify
Step 1: event_id in the DataLayer
The purchase event in the DataLayer already contains the transaction_id. This is used as the basis for the event_id:
dataLayer.push({
event: 'purchase',
event_id: 'purchase_' + transactionId,
ecommerce: {
transaction_id: transactionId,
value: orderValue,
currency: 'EUR',
items: [...]
}
});
The purchase_ prefix prevents collisions in case you use the same transaction ID for other events (e.g. refund_1042).
The event_id must be included in the DataLayer push before the Pixel event fires. If you generate the event_id client-side via JavaScript, the same logic must also run server-side. Better: an ID from the backend (Order ID) that is identically available in both contexts.
Step 2: event_id in the Pixel
The Meta Pixel in GTM reads the event_id from the DataLayer:
fbq('track', 'Purchase', {
value: NaN,
currency: NaN,
content_ids: NaN,
content_type: 'product'
}, {
eventID: NaN
});
The fourth parameter of fbq('track', ...) accepts an options object with eventID. Note the casing: eventID (camelCase) in the Pixel, event_id (snake_case) in CAPI.
Step 3: event_id in CAPI
The GTM Server Container sends the CAPI request. The Meta CAPI tag in the server container must use the same event_id. In the server-side configuration:
- The GA4 client in the server container receives the request from the browser
- The
event_idis forwarded as an event parameter - The Meta CAPI tag reads the
event_idand includes it in the CAPI request
{
"data": [{
"event_name": "Purchase",
"event_id": "purchase_1042",
"event_time": 1711382400,
"user_data": {
"em": ["hashed_email"],
"ph": ["hashed_phone"]
},
"custom_data": {
"value": 149.90,
"currency": "EUR"
}
}]
}
Step 4: Web Pixel for checkout events
In the Shopify Checkout, tracking runs via Web Pixels. The Web Pixel has no access to the storefront DataLayer, but it can access Shopify's standard events (checkout_completed). For the event_id, the Web Pixel uses the Order ID from the event payload:
analytics.subscribe('checkout_completed', (event) => {
const orderId = event.data.checkout.order.id;
const eventId = 'purchase_' + orderId;
// Pixel event with event_id
// CAPI receives the same event_id via the server
});
All events that need deduplication
Deduplication does not only apply to Purchase. Every event sent by both Pixel and CAPI needs an event_id.
| Event | event_id source | Example |
|---|---|---|
| Purchase | Transaction ID / Order ID | purchase_1042 |
| AddToCart | Product ID + timestamp bucket (minute precision) | atc_7829384_202603251430 |
| InitiateCheckout | Checkout Token | ic_abc123def456 |
| ViewContent | Product ID + Session ID | vc_7829384_sess_a3f7c2d |
| AddPaymentInfo | Checkout Token | api_abc123def456 |
| Lead | Form ID + submission timestamp | lead_contact_202603251430 |
For events without a natural unique ID (like ViewContent), the combination of object ID and session ID is the best compromise: unique enough for deduplication, but stable across page reloads within the same session.
All your standard events (Purchase, AddToCart, InitiateCheckout) need event_ids, not just Purchase. Without deduplication for AddToCart, Meta sees more add-to-cart events than actually happen, and your funnel analyses are distorted.
Google vs. Meta: Different approaches
Google and Meta handle deduplication differently. If you use both platforms, you need to implement both approaches.
Google: transaction_id
Google Ads and GA4 deduplicate purchase events based on the transaction_id in the ecommerce object. Deduplication happens automatically: GA4 recognizes when the same transaction_id value is sent multiple times and counts the purchase only once.
Specificity: Google only deduplicates purchase events. Other events (like add_to_cart or begin_checkout) are not deduplicated. This is usually not a problem, because Google does not count these events as conversions by default (unless you have explicitly marked them as conversions).
Meta: event_id for all events
Meta deduplicates every event for which an event_id is sent. This covers all standard events and custom events. Without event_id, no deduplication occurs. Meta's approach is more explicit: you must actively implement deduplication, but in return you get full control over all event types.
Comparison
| Aspect | Meta | |
|---|---|---|
| Deduplication key | transaction_id |
event_id |
| Automatic? | Yes, for purchases | No, must be explicitly sent |
| Affected events | Purchase only | All events with event_id |
| Time window | Not documented (hours to days) | 48 hours |
| Without deduplication | Duplicate purchases in GA4 | Duplicate conversions in Meta Ads |
Debugging and validation
Meta Events Manager
The Events Manager shows the deduplication status per event:
- Events Manager > Pixel > Test Events
- Select event (e.g. Purchase)
- Check: "Event received from" shows "Browser" and "Server"
- Check: "Event ID" shows the same value for both sources
- Status: "Deduplicated" or "Not deduplicated"
Common errors
event_id missing in one of the two channels. The Pixel sends eventID, but the CAPI request contains no event_id. Meta cannot deduplicate.
Incorrect casing. Pixel: eventID (camelCase in the options object). CAPI: event_id (snake_case in the JSON body). Confusion causes the field to be ignored.
event_id generated per request instead of per event instance. When Pixel and server each generate a new UUID, the IDs differ. The ID must come from the event (e.g. Order ID), not from the sending mechanism.
Time offset exceeds 48 hours. If the CAPI request is sent 3 days after the Pixel event, it falls outside the deduplication window. In Shopify setups with server-side GTM, the time offset is normally under one second.
Check the casing twice: in the Pixel it is eventID (camelCase) in the fourth parameter of fbq('track', ...). In CAPI it is event_id (snake_case) in the JSON body. JavaScript frameworks sometimes automatically convert between camelCase and snake_case. This can lead to subtle bugs.
Meta Events Manager shows per event whether it came from browser, server, or both and whether deduplication worked. Check this weekly for Purchase events. Status "Deduplicated" means it works. Status "Not deduplicated" means you are counting double.
Monitoring: measuring deduplication rate
To ensure deduplication works consistently, track these metrics:
Meta Events Manager > Diagnostics: Shows warnings for missing or faulty deduplication. Check this page weekly.
Conversion comparison: Compare the number of Purchase events in Meta with actual orders in Shopify. The ratio should be between 0.95 and 1.05 (slight deviations from consent gaps and timing). A ratio above 1.2 indicates deduplication problems.
GA4 custom dimension: Send the event_id as a custom dimension in GA4. This allows you to check in BigQuery whether the same event_id appears multiple times.
Conclusion
Event deduplication is not an optional optimization. Without it, Meta overstates your conversions by 30 to 80%, your ROAS looks better than it is, and your budget planning is based on false data. Smart Bidding on Meta optimizes toward inflated conversion numbers and misallocates budget.
The implementation is not complex, but it requires care: a stable event_id from the DataLayer, correct forwarding to Pixel and CAPI, and regular validation in the Events Manager.
Want to make sure your Meta conversions count correctly? In our tracking setup, event deduplication for all platforms is standard.
You might also like
Enterprise-Grade E-Commerce Tracking on Shopify: No App, No Agency, No Compromise
Your Shopify store loses 20–40% of all conversion data. That costs you ad performance, attribution, and revenue. Here is how to get that data back.
Read article → Tracking & ComplianceThe Hidden ROI: How Tracking Infrastructure Lifts Your Google Ads ROAS by 20–40%
Tracking infrastructure is not an IT expense: it is the most profitable investment in your ad performance. Four layers, one calculation.
Read article →Our service
Tracking & Data Architecture
20–40% of your conversion data is missing. Server-side tracking, Consent Mode v2, 18+ events, and engagement scoring bring it back.