Operator API
The wallet callbacks you implement for 155.io integration
The wallet API is four callbacks that 155.io sends to your wallet: /balance, /bet, /win, and /rollback. You implement these endpoints and respond; 155.io calls them to read balances and move money. This page states the rules that apply to all four — each callback page then covers only its own payload.
The four callbacks
/balance
Return the current balance of a player
/bet
Register a bet the player made
/win
Register a win or loss
/rollback
Rollback a registered bet
Authentication
155.io signs every callback with the X-Marbles-Signature header (RSA-SHA256 over the raw request body, BASE64-encoded). Secure those callbacks in one of two accepted ways:
- Verify the signature (recommended) against the 155.io public key shared at onboarding, and reject requests that fail with a non-2xx status (e.g.
401) — the strongest option, and free since you already hold the key. This is the one case where a non-200 is expected; every business outcome (SUCCESS,INSUFFICIENT_BALANCE_ERROR, …) is still returned as200with the result instatus. - Or strictly IP-allowlist 155.io's outbound addresses at your firewall, gating on the real source IP (the TCP peer, not a client-supplied
X-Forwarded-For).
Either is accepted for go-live — the acceptance test treats signature verification as advisory, not a blocker. The direction is one-way: you never sign your responses, and 155.io does not verify them.
See Security for verification code samples and the full direction-aware picture.
Always respond HTTP 200
Always return HTTP 200
Every callback — success and error — must respond with HTTP 200. The outcome lives in the JSON body's status field. Returning a non-200 status, or a generic error body without a status, is the #1 integration mistake: it can trigger retries or rollbacks on our side.
{
"status": "SUCCESS",
"requestId": "original-request-id",
"clientPlayerId": "player-id",
"currency": "USD",
"balance": 1000000
}The full set of status values has a single canonical home in Error codes — refer there rather than memorising values from the callback pages.
Idempotency
Every money-moving callback must be idempotent — a retry must never move money twice. The dedupe key and the replay response differ per callback:
| Callback | Dedupe on | Replay returns |
|---|---|---|
/balance | — (read-only, no rule) | — |
/bet | transactionId | DUPLICATE_TRANSACTION_ERROR |
/win | transactionId | SUCCESS (DUPLICATE_TRANSACTION_ERROR also accepted) |
/rollback | referenceTransactionId | SUCCESS (DUPLICATE_TRANSACTION_ERROR also accepted) |
/rollback is the exception
On /rollback the per-request transactionId is not stable between attempts — dedupe rollbacks on referenceTransactionId (the bet being refunded), never on transactionId. Deduping rollbacks on transactionId refunds the same stake once per retry. See /rollback § Idempotency.
Round IDs
The roundId on /bet, /win and /rollback is a per-bet round reference: every bet gets its own value, even when several bets belong to the same game round. Most of our games use a chip-placement interface where one player commonly places several chips in one round — that produces several /bet calls, each with a different roundId.
- Do not group reporting by this field expecting one row per game round — grouping by it groups bets.
- The
roundIdreturned byPOST /game/betsis the game round (a different value); use that one withPOST /game/roundto fetch round details and the race clip. - To trace a callback back to its bet: the
transactionIdon/bet(and thereferenceTransactionIdon/win//rollback) is the 155 bet ID — look it up withPOST /game/bets(match onid), or paste it into the round-lookup page at support.marbles.xyz.
Settlement timing & retries
Settlements are not always immediate, and your wallet must accept them late:
- If your
/winor/rollbackdoes not returnSUCCESS(orDUPLICATE_TRANSACTION_ERROR) — including timeouts and non-200s — we retry it: expect several attempts in the first ~20 minutes, then roughly every 15 minutes, for up to ~6 hours after the bet was placed. - Every retry of the same settlement carries the same
transactionId(onlyrequestIdchanges), so your normaltransactionIddedupe absorbs them — returnSUCCESS. - You must accept a
/winfor a bet placed hours earlier, even if the player's session has closed — and during incident recovery we may replay a settlement manually later still, so never gate acceptance on age. - A lost bet is settled as a
/winwithamount: 0, and a voided round as a/rollback, on the same schedule. - Exception — Footfall (crash) cash-out wins: once a player has cashed out, the credit is owed, and that
/winretries with no time cap until you returnSUCCESS. Never reject one as stale. A settlement repeating for hours is our recovery working as designed, not a bug — returnSUCCESS/DUPLICATE_TRANSACTION_ERRORand it stops.
Callback timeouts
| Callback | Deadline |
|---|---|
/balance | 8s |
/bet | 8s |
/win | 30s |
/rollback | 30s |
Exceeding the /bet deadline is treated as a rejection: we send a /rollback for that transaction even if your wallet actually accepted and debited it. Answer /bet well inside 8 seconds, and make /rollback tolerant of a reference transaction you consider successful (see /rollback).
Amounts
All amount and balance fields are integers in the currency's minor units — there are no decimals on the wire. Precision is per-currency, so always look up the scale for the currency on the request. See Currencies for the precision table.
Treat `amount` as an exact integer — never round-trip it
Store, debit, and credit the amount exactly as the integer you receive. Do not convert it to a decimal/display value and back, and do not round it to fewer decimals than the currency's precision — that silently drops value and breaks balance reconciliation. (It's the single most common cause of failed acceptance checks: a high-precision currency like kIDR arrives as a large integer, the wallet round-trips it through a 2-decimal display value, and the debited amount no longer matches.) Precision is for displaying amounts to your players, not for your wallet's debit/credit math.
Next
- Implement the callbacks: /balance, /bet, /win, /rollback
- Handle the Freebets & Rewards flow (free bets, reward grants)
- Validate your integration with Acceptance testing
- Validate your responses: Response simulator
- Pull our games into your lobby: Get Games (
POST /game/games) - Something not working? Troubleshooting