Skip to Content
AppendixTransaction Handling

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 current balance.
{ "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

SettingValue
connect timeout5 seconds
total timeout per attempt10 seconds
retriesup to 3, with 200 / 400 / 800 ms backoff
  • Only transport failures are retried: connection errors, timeouts and HTTP 5xx responses.
  • 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 200 with status: "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 responseHow we treat it
HTTP 200 + status: "OK"success
HTTP 200 + status: "ERROR"error, error_code is passed on to the game provider
HTTP 4xxfailed call, the body is not read
HTTP 5xxtransport 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 transactionWallet operation
betcredit the stake back to the player
windebit the win amount from the player
  • A repeated cancel with the same transaction_id is a duplicate (1007).
  • amount is informational. A cancel may arrive without it, and aborted_action may be absent as well — resolve the original transaction from aborted_transaction_id and 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.

  • 1008 is 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_id anyway. 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

  • amount and balance are 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 balance in the same major-unit format.

Round completion

  • round_id groups the transactions of one game round. ext_round_id is the game provider’s own round id, sent when available.
  • ext_round_finished: 1 is sent on the transaction that closes the round — only for game providers that report round completion. There is no separate “close round” callback.
  • freespins_finished marks 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.

ScenarioWhat it proves
authenticate, balancethe session token is validated, the player and balance are returned
balance in another currencya currency the player does not hold is refused (1003)
bet, winthe stake is debited and the win credited, with the new balance
bet above the balancean unaffordable stake is refused (1006) and nothing is booked
the same bet / win / cancel twicea repeated transaction_id is not booked a second time (1007)
cancel of a betthe stake is credited back in full
cancel of an unknown transactionanswered with 1008, nothing is booked
the cancelled bet arriving afterwardsrefused with 1012, so the stake is not taken after the round was rolled back
cancel without amount and aborted_actionthe original is resolved from aborted_transaction_id alone
cancel of a win with an empty balancethe debit goes through and the balance goes below zero
a sub-cent amountthe full precision is booked, not a rounded value
missing, wrong, stale and replayed signatureall 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.00 and 2.00);
  • a session token for that player — only you can mint one;
  • a game_id from 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.

Last updated on