
A mobile studio launched a new subscription tier alongside their existing IAP catalog. For some time, ARPU on the new tier looked perfectly healthy.
Nobody flagged it. The new SKU was a small slice of total revenue, and the topline dashboard never moved enough to look suspicious.
It was not until someone reconciled that specific SKU against store receipt data that the truth came out: renewal transactions for that one subscription tier had been recorded twice, the whole time. The new purchase flow had never been wired into the same dedup logic the studio's older, established SKUs already had. For that whole period the team believed the new tier was earning more than it actually was, and when they fixed it, the reported number dropped.
The team was sharp and experienced. That is what makes this class of bug so hard to avoid. It is not the failures big enough to show up in an aggregate view that hurt you. It is the ones quietly living in the parts of your catalog that are newest, smallest, or least reconciled.
1. Client and server double-firing. The same IAP fires once from the game client, immediately, for a snappy UX, and once from your backend after receipt validation completes. Both are technically correct events. Both get counted. If entitlement-granting is wired to either firing independently, the player gets credited twice.
2. Multiple BillingClient connections (Android). If an app holds more than one active BillingClient connection, usually from a singleton not being enforced correctly, it produces duplicate PurchasesUpdatedListener callbacks and double-grant bugs. This one is especially live right now. Google's publishing gate for Billing Library 8 closed on 31 August 2026, and studios that requested an extension have until 1 November 2026, so a lot of Android teams are inside this exact code as you read this. The gate after it, retiring version 8, lands on 31 August 2027.
3. Network retries on unreliable connections. A mobile player's connection drops mid-transaction. The client, not knowing whether the receipt validation call succeeded, retries it. Now the same purchase exists twice in your pipeline while payment went through only once.
4. Reconnect and session-resume logic. Common in live-service and battle-royale titles. A player's session drops mid-match and resumes, and reconnect handling re-sends events, session starts and sometimes pending transactions, that have already landed. That inflates DAU and transaction counts alike.
5. No unique transaction or receipt ID enforced at ingestion. Apple and Google both issue a unique identifier per receipt (a transaction ID on iOS, a purchase token on Google Play). If your pipeline does not require it and dedupe against it before crediting the SKU, there is structurally nothing stopping a retried receipt from being processed as a brand-new sale.
6. Consumable-item consumption failures. Different from the network failure above. Here the connection is fine and the break happens inside Google Play's own confirmation step. Google's Play Billing documentation acknowledges the consumption request can occasionally fail on its own, independent of connectivity, leaving the item marked unconsumed and vulnerable to being re-processed as a fresh purchase.
7. Client-side caching with a narrow dedup matching window. Unity's own Analytics documentation explains that events are cached locally and only cleared once a 204 confirmation is received. On a weak connection, the client resends from cache. Unity's built-in dedup only catches this if event_timestamp, event_name and user_id all match exactly, so even the platform's own safety net can miss a retried event if the timestamp is regenerated instead of preserved.
8. UI re-entrancy, not a network issue at all. In one documented case, duplicate SESSION STARTED events with identical event IDs were traced back to players tapping a button repeatedly while the client waited on a server response, because the button was not disabled after the first tap. A state-management bug, not an infrastructure one.
9. The SDK fires its own events despite manual configuration. There are documented cases of a widely-used analytics SDK sending automatic EndSession events even when a studio has explicitly configured manual session handling. A case of "we configured it correctly and it still happened."
10. Reinstalls duplicating player identity. On reinstall, engines such as Unity may delete the persistent PlayerPrefs ID used to recognise returning players, so a genuine returning player gets counted as brand-new. This is identity duplication rather than event duplication, but it still inflates your numbers.
Players are mid-session on flaky mobile networks, or losing connection mid-match on console, far more often than a typical e-commerce checkout on wifi. Retry-driven duplication simply has more opportunities to occur in the first place.
And unlike a SaaS double-charge, which finance catches on a bank statement, a duplicated in-game entitlement commonly shows up nowhere until someone specifically audits granted currency against revenue.
Platforms like Unity and Amazon build deduplication logic directly into their architectures. But the underlying causes, network retries, SDK re-delivery, session resumes, keep producing new ways for events to slip past it. The fix is not a single setting. It is a standing discipline.
This piece from MY.GAMES, the studio behind War Robots and one of mobile's longest-running live-service shooters, shows just how much ongoing engineering it takes to keep duplicates out at scale, at more than a billion events a day.
The common playbook: assign a unique idempotency key to every event, either a client-generated ID or a checksum of the request itself; store processed keys in a fast-lookup dedup store with a retention window; enforce a UNIQUE constraint at the database level as a backstop; and for real-time pipelines, keep a rolling state of recently-seen keys so duplicates get caught in flight rather than after the fact.
None of this is exotic. It is standard, well-documented practice, and when applied correctly it works.
But none of these fixes are permanent, for three concrete reasons.
The failure often happens above the layer your dedup logic protects. A silent consumption-request failure, or an SDK re-firing a listener, creates the duplicate before it ever reaches the pipeline a data engineer designed. The fix has to live at the point of the side effect, which is frequently code owned by Apple, Google, or a third-party SDK, not the studio.
Every new code path is a fresh chance for the fix not to be there. A new SKU, a new purchase flow, a mandatory SDK migration forced by someone else's deadline. Each one is new code, and dedup logic tends to get added where a team already got burned, not enforced as a blanket rule everywhere.
A solved fix can quietly stop being true. A common early approach is to dedupe on a batch serial number held in the client's shared preferences. Architecturally sound, right up until the storage underneath it proves less durable than assumed: shared preferences can be rolled back, and once they can, the key cannot be trusted. The durable answer is a checksum of the request body stored server-side, but most teams only get there after the first approach has quietly stopped working. The pattern was not wrong. An assumption inside it was, and that kind of failure does not announce itself.
In other words, this is ongoing, overhead-heavy discipline, closer to security patching than to a bug fix. Nobody expects to finish security once and stop checking. The same standing vigilance is what duplicate-event management actually requires, every time the system grows a new surface for the old problem to reappear on.
A good game analytics agent changes who has to notice duplication, and how fast. Not whether it happens.
Every cause above, retries, SDK re-fires, silent confirmation failures, is not going away. What changes with an agentic layer is who has to notice and work around them.
It keeps a live catalog, not a one-time checklist. It knows the expected shape of every in-app event: purchase, session, progression. That catalog grows automatically as new SKUs and flows get added, rather than whenever someone remembers to update a list. And it identifies and alerts on data changes, including quality changes such as duplication.
It watches continuously, including what did not exist last quarter. Duplicate rates and schema drift get checked on an ongoing basis, on event types nobody thought to add to a manual audit.
It alerts either way. Unprompted, the moment something looks off or ambiguous while querying the data. Or on request: "any duplication issues on the new subscription tier this week?"
The result: duplication keeps happening, but it stops being something you have to remember to go looking for, or something that quietly skews the numbers you are making decisions on.
A dashboard only shows you what you asked it to compute. It has no opinion on whether the underlying transaction count is correct. Duplication usually gets caught the way the studio above caught it: by accident, during a reconciliation nobody scheduled.
An agent built to catalog and govern in-game events works differently, if it is built to: