Duplicate payment events
Duplicate Payment Events: Root Cause and Fix
Admaxxer is a DTC analytics platform with built-in Meta + Google ad ops. If a purchase fires from both the client-side pixel and the server-side Shopify webhook without a shared event_id, Admaxxer counts the revenue twice and every downstream metric — MER, ROAS, LTV, MMM contribution — goes wrong in the same direction. TL;DR: pick one source of truth, emit a deterministic event_id (the Shopify order id works great), and deduplicate at ingest.
Symptoms
- Revenue in Admaxxer is roughly 2x what Shopify reports for the same day.
- Blended MER looks unrealistically good.
- CAPI match rate looks fine but your purchase count is inflated.
- The event log contains two rows per order — one with
source = pixel, one withsource = shopify_webhook. - Cohort LTV curves shift up right after Shopify was connected or the pixel was installed.
Root cause
Admaxxer supports multiple purchase sources because different stacks need different combinations:
- Client-side pixel
purchaseevent. - Server-side Shopify
orders/paidwebhook. - Server-side CAPI event for Meta.
If two of those fire for the same order and the event_id is different, Admaxxer treats them as distinct events. The common mistakes:
- Firing
admaxxer.track('purchase', ...)on thank-you page AND having Shopify webhook subscribed toorders/paid. - Using a random
event_id(uuid per beacon) instead of the order id. - Sending CAPI events with the same
event_idbut differentevent_timesuch that the deduplication window is exceeded.
Fix
Step 1: Decide on one source of truth
For Shopify brands, the webhook is authoritative because it survives ad blockers and client-side bounces. Keep the pixel purchase firing only if you need CAPI match-rate signal, and make sure it sends the same event_id.
Step 2: Emit a deterministic event_id
On the pixel side, pass the Shopify order id (or your checkout's internal order id) as the event_id:
admaxxer('track', 'purchase', {
event_id: 'shopify_' + orderId,
value: total,
currency: 'USD',
order_id: orderId
});
The webhook should emit the same event_id format. Admaxxer deduplicates on event_id.
Step 3: Backfill deduplicate
If doubles already exist, run the Admaxxer "purge duplicate purchase events" job from the Admin panel, or contact support. Do not manually delete rows — it breaks cohort backfills and LTV recomputation.
Step 4: Audit other event types
Duplicates usually hit purchase, but add_to_cart and initiate_checkout can get doubled the same way. Use the same event_id strategy across all ecommerce events.
Step 5: Verify CAPI parity
If you send CAPI events to Meta separately, use the same event_id there too. Meta deduplicates on event_id + event_name within a window — diverging ids break CAPI match rate and Meta's delivery system will treat the same order as two separate conversions.
Step 6: Handle multi-subscription and installment orders
Some stacks split an order across multiple orders/paid events (installment plans, deferred shipping, subscription renewals). Use the parent order id plus a suffix, e.g. shopify_<order_id>_charge_<charge_id>, so each charge is unique but traceable back to the parent order. Without the suffix, renewals silently deduplicate against the original order and revenue is undercounted.
Verify the fix
- Today's Admaxxer revenue matches Shopify gross revenue within rounding error.
- Each order id appears exactly once in the Admaxxer events log.
- Blended MER settles to a realistic number.
- Meta Events Manager's "Deduplication" tab shows a healthy server-vs-browser match, not duplicates.
- Subscription renewals and installment charges appear as separate events with distinct suffixes.
Prevent it next time
- Make
event_iddeterministic from day one. Always derive from the order id or a stable server-assigned id. - Single source of truth. Prefer server-side for authoritative revenue; treat pixel as a signal for attribution, not counting.
- Monitor duplicate rate. Admaxxer can alert when the same
event_idarrives from two sources more than N times an hour. - Write an E2E test. A headless purchase test that asserts exactly one purchase row catches regressions before they hit production.
Related guides
- Shopify orders webhook not firing
- Verify revenue attribution after install
- Troubleshoot duplicate events
FAQs
Q: Should I turn off the pixel purchase event entirely?
A: Only if you do not care about CAPI match rate. Keeping the pixel purchase is useful for Meta CAPI parity as long as the event_id is shared with the webhook.
Q: What about partial refunds — do those create duplicates?
A: No. Refunds fire on orders/refunded, not orders/paid, so they get their own event_id suffix (e.g. shopify_refund_<id>) and do not double-count.
Q: How does deduplication work across Admaxxer, Meta CAPI, and Google Ads Enhanced Conversions?
A: Each platform deduplicates within its own window. Using a consistent event_id across all three minimizes both under- and over-counting.
Frequently Asked Questions
Should I turn off the pixel purchase event entirely?
Only if you do not care about CAPI match rate. Keeping the pixel purchase is useful for Meta CAPI parity as long as the event_id is shared with the webhook.
What about partial refunds — do those create duplicates?
No. Refunds fire on orders/refunded with a distinct event_id suffix, so they do not double-count paid orders.
How does deduplication work across Admaxxer, Meta CAPI, and Google Ads Enhanced Conversions?
Each platform deduplicates within its own window. A consistent event_id across all three minimizes both under- and over-counting.
Put This Knowledge Into Action
Bring Meta and Google ads into one self-hosted workspace.
Get Started Free