Articles

    Casino Game-Round Reconciliation: Finding GGR Gaps Across Aggregators

    By Livia MorgensternAugust 29, 2026Updated October 11, 20268 min read

    Where does a win of 16 go when it never reaches the player's balance? The ticket arrives, the wallet shows the debit, the aggregator log shows a callback that timed out, the provider back office shows the round settled, and the monthly statement folds all of it into one total. Round-level reconciliation is how an operator answers that ticket from evidence, and it is the same work that makes the GGR figure defensible to finance.

    GGR gaps start where systems disagree

    A GGR discrepancy is what you get when four systems describe the same event and disagree about it: the operator wallet records a movement, the aggregator logs a request and its status, the provider reports an outcome, and the settlement statement carries a commercial total. As long as those records are only compared in aggregate, finance cannot defend a balance and engineering has no bounded place to search when something breaks.

    Reconciliation exists to fix that. It establishes the canonical economic state of each round, quarantines records still in flight, and explains the exceptions that remain. The feeds do not have to agree at midnight. A wager the provider has already settled but the wallet still shows as open is exactly the kind of item that later turns into a support case, a rollback, or a duplicate debit.

    What follows is about game rounds and the statements derived from them, not deposits, PSP routing, chargebacks, or catalogue performance. How the platform is connected decides what evidence you even have to work with, which is the operator trade-off between game aggregators and direct integrations.

    A game round is a state machine, not a row

    A one-bet, one-win model breaks the moment a real round involves a timeout, a retry, a free spin, an interrupted session, a cancellation, or a delayed callback. The goal is to keep every message that arrives without ever counting the same liability twice.

    Store immutable events. Each one carries the operator, aggregator, and provider transaction IDs; the round and game IDs; player, currency, amount, and event type; the received and source timestamps; an idempotency key; a reference to the raw payload; and the processing result. Status is never overwritten in place: a correction points back to the event it changes, so the history stays intact.

    Those events describe a single round the way it should run. In the ordinary case a player bets 10, the wallet accepts the debit, the aggregator forwards the wager to the provider, the provider returns a win of 16, the aggregator asks the wallet to credit it, and the wallet accepts the credit and updates the balance.

    A timeout changes what every later message means. If the wallet debits 10, sends the aggregator nothing back, and then receives a retry carrying the same idempotency key, it has to return the first result and move no money. A second debit is wrong even when the supplier statement happens to net to the expected GGR. A round that never finished is neither loss nor win; it is unresolved liability until an expiry, recovery, or rollback policy resolves it.

    Model the economic legs, whatever the vendor calls them: a debit is a stake, a credit is a win, a rollback reverses an accepted leg, and a retry is no leg at all unless it is processed as one. Anchoring on the legs keeps treatment stable no matter whether the integration calls something cancel, refund, void, reversal, or rollback.

    Daily totals conceal the round-level fault

    Daily GGR can match to the penny while balances are wrong, because a duplicated stake and a missing stake of equal size cancel each other out. Two totals can also differ for no reason other than provider completion and wallet posting sitting on different cut-offs. Normalise status, currency, and time boundary before comparing anything, and then ask which rounds are creating the variance.

    Treat a surprising number as a data fault until a sample of rounds says otherwise, and treat a perfect one with almost the same suspicion. A day when GGR jumps 30% on one provider is more likely a duplicated credit feed than a lucky week for the house, and a day when every total matches exactly still deserves a sample of rounds before anyone signs it off.

    There is a serious case for skipping round-level work altogether. The supplier invoices on its statement, the statement is what finance pays, and immutable event storage, replay tooling and a daily exception queue cost engineering months. For an operator on one aggregator whose statement variance stays under 0.1% of GGR, a monthly statement check plus ticket-driven investigation can be the rational choice. The argument stops holding the moment a second supplier, a second currency or a balance-affecting incident arrives, because a statement-level variance cannot say which players were affected. Put numbers on it: at £2m monthly GGR, 0.1% is £2,000, which looks immaterial until it turns out to be 40 players each missing a £50 win.

    Mismatch class Typical evidence Treatment while open Owner
    Missing operator leg Provider result, no wallet match Outside settled GGR pending recovery Platform engineering
    Duplicate economic leg Two accepted debits/credits for one idempotency or round reference Reverse confirmed duplicate; retain audit trail Wallet team
    Rolled-back round Original leg and linked reversal Net to zero when rollback is valid Finance operations
    Late settlement Round predates cut-off; settlement arrives later Timing bridge, not permanent gap Reconciliation owner
    Unfinished round Accepted stake, no terminal supplier state Documented open-round policy Provider operations
    Identifier collision One reference maps to multiple player, game, or currency values Block automated netting Integration engineering

    Resist the urge to sweep every variance into a single adjustment account. It may balance the monthly report while it hides supplier exposure, wallet defects, timing variance, an accounting-policy question, and a repeat incident, all in the same line.

    A daily proof pack that finance can check

    Every daily run should produce a proof pack that someone outside the reconciliation team can check. A red cell in a spreadsheet tells finance there is a problem and nothing about where it lives. Freeze the window and state plainly whether it closes on wallet acceptance, provider completion, or an agreed operational cut-off. Reconcile in the transaction currency first and convert to reporting currency only after rounds are matched, or exchange-rate rounding will start to look like a missing round.

    Settled GGR = accepted stake legs minus accepted win legs minus valid rollback impact

    Rollback impact depends on which original leg was reversed: a stake reversal reduces stakes, a win reversal reduces wins. Subtracting every rollback from GGR misstates any round that was rolled back after it paid out.

    A worked extract shows how those legs interact. Take a five-round, single-currency extract with made-up amounts. A has stake 10 and win 16, GGR -6; B stake 10 and no win, GGR 10; C stake 5 and valid stake rollback 5, GGR zero; D stake 8 and win 8, GGR zero; E is a stake of 12 that the wallet accepted on a retry of a request the aggregator had already rejected, so no provider round stands behind it. Raw wallet stakes are 45 and wins 24, GGR 21. Removing E's orphan 12 (which has to go back to the player) leaves canonical stakes 33; applying C's valid stake rollback brings settled stakes to 28 and settled GGR to 4, which matches the per-round view (-6 + 10 + 0 + 0). If the aggregator statement also shows 4, this is a wallet processing defect with no commercial supplier discrepancy behind it: finance corrects the ledger and should not dispute settlement.

    Each exception needs links to its source payload and processed event, a reason code, the decision time, the reviewer, and a resolution reference. With that in place finance can bridge daily GGR to general-ledger policy and engineers can replay the messages. Before any of this is automated, a qualified finance owner has to approve the definitions of stake, winnings, valid reversal, open-round reserve, tax treatment, and statement cut-off. Data modelling cannot settle accounting judgments that no one has agreed.

    The daily query should surface exceptions

    Agreed definitions are what make a repeatable daily query possible. Build a canonical round view and keep exceptions apart from settled activity. Joining on raw round IDs is unsafe, because references get reused across games, bonus features emit their own linked transactions, and the wallet and the supplier do not always order events the same way. Join on the strongest shared keys you have, then validate player, currency, game, amount, and time window.

    The pseudocode below keeps accepted legs, marks retries conservatively, and surfaces rounds with a missing or non-terminal provider state:

    WITH wallet AS (
      SELECT round_id, provider_txn_id, player_id, currency, event_type, amount,
             idempotency_key, accepted_at
      FROM wallet_events
      WHERE accepted_at >= :start_at AND accepted_at < :end_at
        AND processing_status = 'ACCEPTED'
    ), deduped AS (
      SELECT * FROM (
        SELECT w.*, ROW_NUMBER() OVER (
          PARTITION BY provider_txn_id, event_type, amount, currency
          ORDER BY accepted_at) AS retry_rank
        FROM wallet w
      ) x WHERE retry_rank = 1
    ), matched AS (
      SELECT w.*, p.round_state, p.settled_at
      FROM deduped w LEFT JOIN aggregator_round_feed p
        ON p.provider_txn_id = w.provider_txn_id
       AND p.player_id = w.player_id AND p.currency = w.currency
    )
    SELECT round_id, provider_txn_id,
      SUM(CASE WHEN event_type='BET' THEN amount ELSE 0 END) AS stakes,
      SUM(CASE WHEN event_type='WIN' THEN amount ELSE 0 END) AS wins,
      MAX(round_state) AS provider_state
    FROM matched GROUP BY round_id, provider_txn_id
    HAVING MAX(round_state) IS NULL
        OR MAX(round_state) NOT IN ('SETTLED','ROLLED_BACK')
        OR COUNT(DISTINCT idempotency_key) <> COUNT(*);
    

    Its output is a worklist of exceptions; the accounting comes after they are resolved. Amount and event type alone cannot establish that two records are the same transaction, since legitimate transactions can share both, so production logic needs real transaction identity and replay semantics and should flag ambiguity for a human to resolve. Run the mirror checks too: supplier records with no wallet match, and wallet legs with no provider match once tolerance is applied. Keep those outputs separate, because netting will happily let an unmatched win cancel an unmatched bet and take your repair evidence with it.

    A worklist only helps if every line on it has a known repair, and each exception class has its own. A missing settled round calls for retrieving the supplier payload, checking correlation IDs, and making an idempotent replay decision. A late round needs a timing bridge and a next-day clearance check. An unfinished round needs a documented provider recovery or expiry. Treat them all as simply missing and you get bad escalations and, sometimes, a wrong balance action.

    Rollbacks deserve real scrutiny, because one can recover a failed wager request, reflect a supplier cancellation, or correct operator processing, and those are not the same thing. Find the original leg and confirm amount, currency, player, and transaction lineage. A rollback with no original to match belongs in the exception queue; netting it away as a negative stake destroys the evidence.

    Transport retries and business retries often arrive under the same label and must not be treated alike. A transport retry returns the original accepted result with no further ledger movement; a business retry may be valid only after an explicit failure. Keep request hashes, idempotency keys, and response codes long enough to tell the two apart. Finance should receive canonical counts from this work, while the technical log stays with engineering.

    Bonus mechanics add the last wrinkle. For bonus-funded or free rounds, keep the funding source separate from the game result. The stake and outcome logic still holds, but the accounting destination can differ from cash wagering, and folding it into cash GGR without a policy blurs bonus cost, game margin, and statement variance together.

    Scale turns a matching job into a control system

    Once volume climbs, resolving exceptions by hand stops working and the process needs controls of its own: queues, ageing rules, supplier mappings, and thresholds that separate a late callback from an incident. Unmatched value alone is a poor alarm. A crowd of low-value exceptions can be the signature of a broken retry release, while a single high-value unresolved round can warrant immediate review on its own.

    Keep standing views for unmatched wallet legs, unmatched supplier events, open rounds past tolerance, and duplicate-or-collision candidates, and slice each by provider, aggregator, game, currency, integration version, and hour. A spike right after a release looks nothing like a persistent provider-specific tail, and good reconciliation points the incident manager straight at the likely fault.

    Volume is what turns this from a spreadsheet task into a system. Take a spin cycle of 2.5 seconds: one player in continuous play then generates 1,440 rounds an hour, each with a stake leg and usually a win leg. Ten thousand concurrent sessions at a third of that pace produce close to five million rounds an hour. A callback failure rate of 0.01% then yields about 480 exceptions an hour, far more than any manual queue absorbs.

    Stake caps offer a free sanity check. Where a market caps the slot stake per spin, and some cap it lower for younger adults, a stake leg above the applicable cap is either a mapping error (typically minor units misread, 500 pence posted as £500) or a compliance incident. Either way it goes to the top of the queue, whatever its value.

    Match supplier statement lines to the canonical round ledger only after the exceptions are classified. A statement is evidence of settlement and commercial accounting, not of player balances; it is aggregated and may run on a different cut-off. The wallet stays the record of accepted player-facing movements, subject to platform controls and policy.

    Define what closure means: ageing bands, an accountable owner, escalation criteria, and a rule for old unresolved items, whether they are accrued, reversed, or moved into a separately approved adjustment process. Local licence conditions and accounting rules can change that treatment, so each operating entity needs legal and finance review.

    Measure the control with two numbers. The success metric is the share of rounds matched on day one, with a target set per provider. The guardrail is the value of exceptions older than 72 hours. A rising match rate next to a growing aged tail means the easy cases are being cleared and the hard ones abandoned, which is the pattern that surfaces later as a month-end write-off.

    The cheapest control runs before a release

    The release spike described above is the reason the cheapest control sits before an integration change goes live, well ahead of any month-end close. Before releasing or changing an integration, confirm:

    • Finance approved state mappings, cut-off, currency treatment, and rollback accounting.
    • Engineering retains immutable raw events and correlation keys for wallet, aggregator, and provider messages.
    • Daily processing separates settled matches, timing items, open rounds, duplicates, and unmatched records.
    • Exceptions have owner, ageing status, evidence links, and recorded resolution.
    • Monitoring alerts on exception count and value by provider and integration version, with player-safety and support escalation for balance-affecting faults.

    The ledger is half of the problem; the player is the other half. High-intensity sessions amplify duplicated debits and missing wins, so a player hit by a balance-affecting exception should be suppressed from promotions until the balance is corrected, with support briefed and the case passed through responsible-gambling procedures where the session pattern warrants it. A customer chasing a win that never arrived is the worst possible recipient of a bonus offer. If the operator already keeps a suppression list for customers showing strong indicators of harm, that list can carry these accounts too until the round is resolved.

    Run daily, reconciliation gives every settled round a proven lineage, every late item a timing explanation and every unresolved record an owner and an ageing rule. Finance gets a GGR bridge it can support, engineers a defect queue with edges, and supplier managers a factual basis for settlement. The player whose win of 16 never reached the balance is the case the whole design exists for.

    Where to start depends on one number. If more than 0.5% of yesterday's rounds (pick your own threshold) cannot be matched on day one, stop building dashboards and fix event storage and idempotency on the noisiest provider first, with one aggregator, one currency and a handful of event types. If the match rate is already high, automate the next most frequent exception class and leave rare edge cases to a reviewer until they recur.