Transaction Handling
How the wallet callbacks behave around retries, duplicates, rollbacks and reconciliation. The individual callbacks are documented under API Callbacks; this page covers the rules that apply across all of them.
Duplicate transactions
Your wallet must be idempotent on transaction_id, per action — a
bet, a win and a
cancel each carry their own transaction_id.
If a transaction_id arrives that you have already booked:
- do not book it again,
- respond with
status: "ERROR", error 1007 and the player’s currentbalance.
{
"status": "ERROR",
"error_code": 1007,
"error_message": "Duplicate transaction request",
"balance": 100.05
}We treat 1007 as “already processed”, not as a failure.
Keep duplicate detection permanent — store it with the transaction, not in a short-lived cache. A game provider may resend the same win hours or even days later.
Timeouts and retries
| Setting | Value |
|---|---|
| connect timeout | 5 seconds |
| total timeout per attempt | 10 seconds |
| retries | up to 3, with 200 / 400 / 800 ms backoff |
- Only transport failures are retried: connection errors, timeouts and
HTTP 5xxresponses. - Every retry carries the same payload and the same
transaction_id. A request that timed out on our side may already have been processed on yours, so the retry must be answered as a duplicate. - An application-level answer (
HTTP 200withstatus: "ERROR") is final and is never retried by us. - If all attempts fail, the failure is passed to the game provider and its own policy takes over: for a bet this is usually a cancel, for a win usually a later resend of the same win.
HTTP status of error responses
Always answer with HTTP 200 and the error payload in
the body.
| Your response | How we treat it |
|---|---|
HTTP 200 + status: "OK" | success |
HTTP 200 + status: "ERROR" | error, error_code is passed on to the game provider |
HTTP 4xx | failed call, the body is not read |
HTTP 5xx | transport failure, retried as described above |
redirect (3xx) | not followed on signed callbacks — failed call |
The only exception is a rejected callback signature,
which you may answer with an HTTP error status such as HTTP 401.
Cancel semantics
A cancel references the original transaction with
aborted_transaction_id, and — when known — its type in aborted_action
(bet or win). Reverse the original booking in full:
| Cancelled transaction | Wallet operation |
|---|---|
| bet | credit the stake back to the player |
| win | debit the win amount from the player |
- A repeated cancel with the same
transaction_idis a duplicate (1007). amountis informational. A cancel may arrive without it, andaborted_actionmay be absent as well — resolve the original transaction fromaborted_transaction_idand reverse what you booked for it, do not trust the amount on the cancel over your own record.- Cancels are ordinary traffic, not an exception path. A bet cancellation follows any round the game could not complete; a win cancellation follows a round that was voided or corrected after it had already been paid.
Cancelling a win takes the balance below zero
Accept it. The debit must go through even when the player no longer has the money:
- The win was paid out and may already have been wagered or withdrawn. Making the reversal conditional on available funds means the cases that need it most are the ones you refuse.
- A refused rollback is not sent again. The round is already reversed on the game provider’s side, and your answer ends the rollback — so a refusal leaves your ledger permanently richer than ours, with nothing to reconcile against.
Book it, answer status: "OK" and return the resulting balance, negative if
that is what it is. What you do with a negative balance afterwards is entirely
your policy — block play, require a top-up, write it off — as long as the
callback itself is accepted.
This is part of the contract, not an edge case we are asking you to guess at. It is one of the scenarios we run against your wallet before go-live, see Certification.
Unknown original transaction (1008)
If the transaction referenced by aborted_transaction_id was never booked,
respond with error 1008 and the current balance.
1008is a final answer: we do not retry the cancel, and it is passed on to the game provider as its own “transaction not found” response, which ends the provider’s rollback retries.- Store the cancelled
aborted_transaction_idanyway. If the original bet arrives later (it can overtake the cancel on the network), reject it with error 1012 instead of booking it.
Amount precision
amountandbalanceare decimal values in the major currency unit (1.50= one euro fifty), not cents.- Almost every transaction you receive carries 2 decimal places, in every currency.
- Parse amounts as decimals, never as binary floats, and do not round on arrival: a truncated amount is money that stays on one side of the ledger.
- Return
balancein the same major-unit format.
Round completion
round_idgroups the transactions of one game round.ext_round_idis the game provider’s own round id, sent when available.ext_round_finished: 1is sent on the transaction that closes the round — only for game providers that report round completion. There is no separate “close round” callback.freespins_finishedmarks the last transaction of a Gift Spin session.
Treat every bet, win and cancel as final on its own. Do not wait for a round to close before settling, and do not expect a zero-amount win on every losing round — many game providers never send one.
Player IP address
The user_ip parameter of Game Launch accepts both
IPv4 and IPv6 addresses. It is passed on to the game provider; for the few
providers that only accept IPv4 we substitute a valid address ourselves, so no
action is needed on your side.
Certification
Before a casino goes live — and any time afterwards on request — we run a wallet
certification suite against your WALLET_URL. It sends the callbacks documented
here exactly as a real game session sends them: same wire format, same
signature, same payloads. Nothing on this page has
to be reproduced by playing and hoping for the right sequence; every case below
is triggered on demand.
| Scenario | What it proves |
|---|---|
| authenticate, balance | the session token is validated, the player and balance are returned |
| balance in another currency | a currency the player does not hold is refused (1003) |
| bet, win | the stake is debited and the win credited, with the new balance |
| bet above the balance | an unaffordable stake is refused (1006) and nothing is booked |
| the same bet / win / cancel twice | a repeated transaction_id is not booked a second time (1007) |
| cancel of a bet | the stake is credited back in full |
| cancel of an unknown transaction | answered with 1008, nothing is booked |
| the cancelled bet arriving afterwards | refused with 1012, so the stake is not taken after the round was rolled back |
cancel without amount and aborted_action | the original is resolved from aborted_transaction_id alone |
| cancel of a win with an empty balance | the debit goes through and the balance goes below zero |
| a sub-cent amount | the full precision is booked, not a rounded value |
| missing, wrong, stale and replayed signature | all four checks of a signed callback are enforced |
The run ends with a balance reconciliation: everything it books it cancels again, and the last call asserts the test player is back on the balance it started from. Any difference is reported as drift, with the amount.
What we need from you
- a test player on your side, with enough balance to cover one bet and one
win (small amounts, the defaults are
1.00and2.00); - a session token for that player — only you can mint one;
- a
game_idfrom your Gamelist; - our IP allowed on the wallet endpoint.
A staging wallet is fine, and preferred. The suite books and cancels real transactions, so never point it at a live player’s account.
You get the full report: per callback the request and response with headers and bodies, the expected answer next to the received one, and a pass rate.