What a casino wallet integration actually has to do
Five endpoints: balance, debit, credit, refund and rollback. The only hard requirement is that debit and credit are idempotent on the transaction id you are given — everything else is detail.
The direction of travel surprises people
You call the games API to open a session. From then on, we call you. Every bet is a request from us to your wallet, and your wallet is the authority on whether the player could afford it.
This is the opposite of the arrangement most integrations assume, and it is the right way round: you keep the balances, the KYC, the deposits and the player relationship. We never hold a player balance, which means there is nothing of your players' money on our side to reconcile, lose or be asked about.
The five calls
- balance — what has this player got? Moves nothing, safe to retry, safe to call against production.
- debit — a bet. Must fail if the balance is insufficient, and that failure is a normal outcome rather than an error.
- credit — a win.
- refund — a specific earlier debit could not be honoured; give it back.
- rollback — void an earlier movement in full. Always the full value; there is no partial rollback, because a partial one is a second transaction wearing the wrong name.
Idempotency is the whole contract
Networks fail after your wallet has committed and before we hear about it. When that happens we retry with the same transaction id, and your wallet must recognise it and move no money the second time. The full wallet contract writes out every field on the wire.
Answer the replay with the resulting balance and `duplicate: true`. That flag is optional and worth setting: it lets both sides tell a genuine second bet from a retry of the first, which is the difference between a reconciliation that takes a minute and one that takes a week.
POST {your_base_url}/wallet/debit
{
"transactionId": "bet_9f21c4",
"playerId": "your_player_42",
"amount": "10.00",
"currency": "GHS"
}
// Your answer, first time and every time after:
{ "balance": "489.50", "currency": "GHS" }
// The same id arriving again — no money moves:
{ "balance": "489.50", "currency": "GHS", "duplicate": true }Refusing a bet properly
Answer with a non-2xx and a code. Insufficient funds is not an error in your system or ours — it is the most common legitimate answer a wallet gives, and it should be fast.
The codes worth mapping are insufficient_funds, player_blocked, player_not_found, currency_mismatch, transaction_not_found and already_reversed. Anything we do not recognise is treated as a terminal rejection and preserved verbatim in the transaction record, so an unmapped code is diagnosable rather than lost.
Test it before your first real bet
Two things are worth proving before you take a player: that a replayed transaction id moves money once, and that an insufficient balance is refused cleanly rather than timing out.
Both are testable against a sandbox in an afternoon. Neither is testable for the first time on a Saturday night with real players on the platform, which is when integrations that skipped this discover the answer.
Try it against a real API
Sandbox keys are issued instantly, with play money and every game enabled. No card, no call, and the documentation is public whether or not you sign up.
More notes
- How a provably fair crash round is actually generatedA crash multiplier is derived from a hash of a server seed the operator committed to before the round, combined with a client seed the player controls. Publish the commitment first and anyone can check afterwards that the result was not chosen.
- RTP, house edge and the number operators should actually checkRTP is the long-run share of stakes returned to players; the house edge is what is left. The number worth checking is not the advertised RTP but whether the running game reproduces it — which requires the operator to be able to see their own round history.
- Idempotency in a betting API, and why the key is yoursThe safest design gives the caller the idempotency key rather than generating one. If you mint the transaction id, a retry after a timeout is unambiguous — and a timeout is the one failure every integration eventually meets.