Idempotency: the four rules that protect money
Certification tests these hardest. Build them in from day one. The full normative text is in Money Path Rules.
Rule 1. A repeated op_id gets the same answer
Keep every op_id with its stored answer for at least 4 months. GA resends for 72 hours, and the margin covers reconciliation disputes.
- Same
op_id, same content: return the stored answer, move no money. Even if the balance is now too low or the session expired. - Same
op_id, different content (player, operation type, currency, amount with exponent, round, original, grant): refuse withERROR_CODE_IDEMPOTENCY_CONFLICT, move no money. - Two identical requests at the same moment: the second waits for the first and gets the same answer. Never apply twice.
Rule 2. Check things in this order
signature -> schema -> op_id lookup -> player + currency/exponent
-> [bets only: session, player status, limits, balance]
-> round state -> apply and store the answer in one atomic step
The op_id lookup comes before session, player, and balance checks. Otherwise a repeated bet answers “insufficient funds” or “session expired” where it must answer the stored success, and you and GA disagree about whether the bet exists.
Rule 3. Never refuse a win, a rollback, or a grant settlement for session, block, or limits
A win can arrive hours after the session ended, for a blocked player, or past a deposit limit. Accept it. The only final refusals for a credit are: player not found, currency mismatch, invalid request, idempotency conflict, unknown original, already rolled back. After any other refusal, GA resends the credit for 72 hours, and then it lands in manual review.
A promo, bonus, jackpot, or tournament payout may arrive with no bet in the round and with no session. Don’t require either.
Rule 4. A rollback of something you never saw is a success, and you remember it
If a rollback names an original_op_id you don’t have:
- Return
status: okwith"original_found": false. Move no money. - Store that
original_op_idas “rolled back”. - If the original debit or credit arrives later, refuse it with
ERROR_CODE_ALREADY_ROLLED_BACK.
Never return ERROR_CODE_UNKNOWN_ORIGINAL to a rollback. Step 2 is what stops you from charging a player for a bet GA already cancelled.
Timeouts and retries
The most important number: GA waits 5 seconds for each wallet call. Answer faster than that, or GA treats the call as lost.
GA sees only two kinds of outcome:
- Refused. A valid envelope with a non-retryable code, such as
INSUFFICIENT_FUNDS. Final: GA doesn’t resend thatop_id. - No answer. A timeout, a dropped connection, a bare HTTP status with no envelope, or a valid envelope with a retryable code (
RATE_LIMITED,MAINTENANCE,INTERNAL). GA doesn’t know whether you applied the operation and retries with the sameop_idand a newrequest_id.
| Operation | On “no answer” GA does |
|---|---|
debit, debit_credit | Up to 4 attempts, 100 ms, 500 ms, and 2 s apart, all inside one 8-second budget for the bet. Each attempt waits at most 5 s or what’s left of the budget, whichever is shorter. GA starts a retry only if the budget still holds the pause before it. So a wallet that doesn’t answer at all gets 2 attempts (5 s, then the rest of the 8 s). All 4 happen only when your wallet answers fast with a retryable code. Still nothing: the bet fails towards the game and GA sends one rollback for that op_id. |
credit, settle_grant | The same quick attempts, then a background resend with the same op_id for up to 72 hours. Backoff starts at 1 second and doubles up to a cap of 10 minutes. The limit is the 72 hours, not a number of attempts. GA never rolls back a win. |
rollback | The same quick attempts, then the same 72-hour resend. Stops at the first valid envelope: success or refusal. |
notify | One attempt, no retry on HTTP/JSON. On gRPC it gets the same quick attempts as every other call. |
GA honours a Retry-After header or retry_after field, capped at 10 seconds inside the quick attempts. When the 72 hours are over, GA flags the operation for manual review and alerts its team. GA never drops anything silently.
What this means for your wallet:
- A bet can reach you after GA already rolled it back. Rule 4 (chapter 3) handles it.
- A win can reach you days later, after the session ended. Rule 3 handles it.
- Every retry carries the same
op_id, so Rule 1 makes retries harmless.