← All articles

Analytics · 9 min read · 2026-08-11

Event Deduplication: Fix Double-Counted Conversions

A team moves its most important conversions to server-side tracking for reliability, leaves the old client-side tag in place, and a week later celebrates a suspicious surge in signups. Nobody launched anything — they are counting every conversion twice. This is one of the most common and most quietly damaging instrumentation bugs in SaaS analytics: two tracking paths reporting the same event, with nothing telling the analytics tool they are the same. The reported numbers roughly double, the funnel looks wonderful, and every decision downstream is built on inflated data.

The fix is event deduplication, and it hinges on one idea: give every copy of an event a shared unique key so the receiving system can recognize the duplicates and count them once. This post explains how deduplication works, the specific way GA4 handles it, and the transaction-ID pitfalls that turn a dedup fix into an under-counting bug. It is a companion to the broader event-tracking anti-patterns that quietly corrupt SaaS metrics before anyone thinks to check.

Why dual tracking double-counts

The double-counting problem is a direct consequence of redundancy done carelessly. When you send a purchase from client-side JavaScript on the confirmation page and also send it server-side when the order is written to your database, GA4 receives two purchase events for one sale. It has no inherent way to know they describe the same transaction — to GA4 they are two independent events arriving from two sources — so it counts two conversions and roughly doubles the revenue attributed to that sale. As the team at OptimizeSmart notes, double-counting "typically occurs when you send purchase data from both the client side and the server side" without deduplication in place (OptimizeSmart).

This is a growing risk precisely because moving to server-side tracking is the right call for accuracy and privacy — the same move recommended for turnkey server-side implementation. Teams add the server path for reliability but forget to retire or deduplicate the client path, and the two now report in parallel. The redundancy that was supposed to make measurement more robust instead makes it wrong in the most flattering direction, which is the hardest kind of error to catch because nobody investigates a number that looks good.

How deduplication actually works

Deduplication works by attaching a shared unique identifier to every copy of an event so the receiving system can match them. When two events arrive carrying the same key, the system treats them as one and drops the extra. The key can be a transaction ID for a purchase, or a generic event ID for any event you send from multiple paths. The mechanism is simple, but it only works if the same ID is genuinely present and identical on both copies — which means the ID has to be generated once, upstream, and passed to every tracking path rather than minted independently by each.

That last point is where implementations fail. If the client generates one ID and the server generates another for the same purchase, the two events carry different keys and deduplication never fires — you are back to double-counting despite having "set up dedup." The correct pattern is to generate the identifier at the source of truth: create the transaction ID when the order is written server-side, then hand that exact value to the client-side tag so both copies speak the same key. An ID strategy that is not shared is not a dedup strategy at all.

GA4's transaction_id window — a partial safety net

GA4 has built-in deduplication for purchase events keyed on transaction_id: receive two purchases with the same ID close together and it collapses them into one. This catches a lot of duplicates on its own and is worth relying on — but it has a hard limit. The deduplication only holds within a processing window commonly cited as roughly 24 to 48 hours. As Analytics Mania explains, two events with the same ID that arrive far enough apart "can both still count" (Analytics Mania). A delayed server-side retry, a late offline conversion import, or a cross-day duplicate can slip past the window and be counted twice.

Google's own guidance is to lean on this by keeping transaction IDs unique per order so the native dedup can do its job (Analytics Help). But the window means you cannot treat GA4's built-in dedup as a complete solution — it is a backstop, not a guarantee. For events that can legitimately arrive far apart, or for non-purchase events GA4 does not auto-dedupe, you need your own event_id discipline rather than trusting the platform to sort it out.

The under-counting trap on the other side

The opposite failure is just as damaging and easier to trigger than teams expect: reusing a transaction ID across genuinely different orders. Because GA4 dedupes by transaction_id, two real purchases that accidentally share an ID get merged into one — and now you are under-counting, erasing legitimate revenue. This happens when IDs are derived from something non-unique like a cart ID that gets recycled, a test order ID that leaks into production, or a sequence that resets. The dedup mechanism you added to stop double-counting quietly starts deleting real conversions.

There is also a user-level dimension. Deduplicating the conversion is not the same as deduplicating the user: for GA4 to recognize the client and server copies as the same person, the client_id must align across both paths. Get the transaction_id right but the client_id wrong and GA4 may count one conversion but two users, distorting per-user metrics even when the conversion total is correct. Both identifiers have to be handled deliberately, which is why event identity belongs in your tracking-plan schema, specified once, rather than improvised per integration.

Getting the ID strategy right

The whole problem reduces to one discipline: control the unique identifier. Generate it server-side at the moment the action occurs, make it unique per event, and pass the identical value to every path that reports the event. Align client_id across paths so user counts dedupe too. And decide, per event, whether it should be sent client-side, server-side, or both-with-dedup — never let dual-firing happen by accident because the old tag was left in place. A one-line rule per critical event in your tracking plan prevents both the double-count and the under-count.

Then verify, because deduplication is exactly the kind of thing that looks fine until you check. Compare your analytics conversion count against the source of truth — billing or your orders table — for a known window; a clean dedup setup should match closely, a doubled one will read roughly 2x, and an over-merged one will read low. Fold that reconciliation into a routine data-quality audit so a silent doubling never survives to a dashboard. Duplicate events are the rare analytics bug that make your numbers look better, which is precisely why they go unnoticed until someone finally reconciles against reality.

FAQ

What is event deduplication?

Event deduplication is the process of recognizing that two incoming events represent the same real-world action and counting them only once. It matters most when you send the same conversion from more than one place — for example, a purchase fired both by client-side JavaScript and by your server — because without deduplication your analytics tool treats each copy as a distinct conversion and inflates the count. Deduplication works by attaching a shared unique identifier, such as a transaction ID or event ID, to every copy of the event, so the receiving system can match duplicates by that key and drop the extras.

How does GA4 deduplicate events?

GA4 deduplicates purchase events by transaction_id: when it receives two purchase events carrying the same transaction_id close together, it collapses them into one. The important caveat is that this only holds within a limited processing window, commonly cited as roughly 24 to 48 hours. Two events with the same ID that arrive far enough apart can both be counted, so the built-in safety net is real but not absolute. The transaction ID must also be genuinely unique per order — reusing IDs across different orders causes GA4 to wrongly merge unrelated purchases, which undercounts instead of double-counts.

Why does running client and server-side tracking cause double counting?

Because each path independently reports the same event. If your client-side tag fires a purchase when the confirmation page loads and your server also sends a purchase event when the order is written to the database, GA4 receives two purchase events for one sale. Without a shared deduplication key it has no way to know they are the same transaction, so it records two conversions and your reported revenue and conversion count roughly double for every dual-tagged event. The fix is to send the same event_id or transaction_id from both paths so GA4 can match and collapse them, or to decide the event comes from only one path.

How do you set up event deduplication correctly?

Generate the unique ID server-side at the moment the action happens — for a purchase, create the transaction ID when the order is written — and pass that same ID to every tracking path, client and server alike. Ensure the ID is unique per event and stable across the paths, so both copies carry an identical value. For user-level deduplication in GA4, the client_id must also match between client and server events, otherwise GA4 may dedupe the conversion but still count two distinct users. Finally, decide deliberately whether a given event should be sent client-side, server-side, or both-with-dedup, rather than letting it happen by accident.

Is double counting or under counting the bigger risk?

Both are serious, and they come from opposite mistakes. Double counting comes from sending an event twice without a shared dedup key, and it silently inflates conversions and revenue so your funnel looks healthier than it is. Under counting comes from reusing a transaction ID across genuinely different orders, which makes GA4 merge unrelated purchases into one and erases real conversions. The first flatters your numbers; the second starves them. Both stem from getting the unique identifier wrong — either absent when it should be shared, or shared when it should be distinct — which is why the ID strategy is the whole game.