What a casino wallet integration actually has to do

8 min readWalletIntegrationEngineering

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.