155API

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

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 as 200 with the result in status.
  • 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:

CallbackDedupe onReplay returns
/balance— (read-only, no rule)
/bettransactionIdDUPLICATE_TRANSACTION_ERROR
/wintransactionIdSUCCESS (DUPLICATE_TRANSACTION_ERROR also accepted)
/rollbackreferenceTransactionIdSUCCESS (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 roundId returned by POST /game/bets is the game round (a different value); use that one with POST /game/round to fetch round details and the race clip.
  • To trace a callback back to its bet: the transactionId on /bet (and the referenceTransactionId on /win//rollback) is the 155 bet ID — look it up with POST /game/bets (match on id), 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 /win or /rollback does not return SUCCESS (or DUPLICATE_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 (only requestId changes), so your normal transactionId dedupe absorbs them — return SUCCESS.
  • You must accept a /win for 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 /win with amount: 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 /win retries with no time cap until you return SUCCESS. Never reject one as stale. A settlement repeating for hours is our recovery working as designed, not a bug — return SUCCESS/DUPLICATE_TRANSACTION_ERROR and it stops.

Callback timeouts

CallbackDeadline
/balance8s
/bet8s
/win30s
/rollback30s

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

On this page