Casino Integration — Seamless Wallet API

This is the contract for the wallet API your casino implements and the prediction-markets platform calls to move money. Your casino remains the single source of truth for the player's balance; we never hold funds. When a player bets, we debit you; when they win or a market is cancelled, we credit you; if a bet fails after we debited, we roll back.


Conventions


Idempotency — the one rule that matters

Every money operation carries a txRef: the platform's idempotency reference, unique per tenant. Your implementation must guarantee:

Applying the same txRef twice has the same effect as applying it once.

Concretely:

This is what makes our retries safe: on a network blip or 5xx we retry the same txRef, and your idempotency guarantees no double charge. Store txRef with a uniqueness constraint and check it before applying.


Endpoints

POST /wallet/debit — reserve a bet stake

Request:

{
  "txRef": "1f6c…",
  "playerId": "player-9",
  "amountCents": 1500,
  "currency": "EUR",
  "context": { "betId": "…", "marketId": "…", "outcomeId": "…" }
}

Success (200):

{ "transactionId": "casino-tx-1", "balanceCents": 8000 }

Both response fields are optional but recommended — we store transactionId for reconciliation and surface balanceCents where useful.

If the player can't cover the stake, reject with either:

We treat both as a clean bet rejection (the player simply can't afford it) — no rollback, no error escalation.

POST /wallet/credit — payout or refund

Same request/response shape as debit. Used to pay a winning bet at resolution or refund a stake when a market is cancelled.

POST /wallet/rollback — reverse a debit

Request:

{
  "txRef": "9ab2…",
  "originalTxRef": "1f6c…",
  "playerId": "player-9",
  "amountCents": 1500,
  "currency": "EUR"
}

Reverses the debit identified by originalTxRef. Returns 200 with an optional { "transactionId", "balanceCents" }. Must be idempotent and a safe no-op if the debit is unknown or already reversed.

GET /wallet/balance?player=<playerId> — read balance (optional)

{ "balanceCents": 8000 }

A convenience for preflight checks. Not required for correctness.


Error codes

HTTP code Meaning Platform reaction
200 Applied (or idempotent replay) Proceed
400 INSUFFICIENT_FUNDS Player can't cover the debit Reject the bet cleanly
402 Insufficient funds (alternative to the 400 code) Reject the bet cleanly
400/409 other Definitive rejection Fail the operation, not retried
5xx Transient server error Retried (bounded), then failed

Network errors and timeouts are treated like 5xx — retried, then surfaced as a transient failure.


How the platform uses these (so you can reason about ordering)

A bet uses a debit-first saga:

  1. debit the stake (amountCents) with txRef.
    • INSUFFICIENT_FUNDS/402 → the bet is rejected; nothing else happens.
  2. The platform places the bet internally.
  3. If placement fails (e.g. price moved beyond tolerance, market just closed), the platform issues a rollback of that debit's txRef.

At market settlement:

Settlement credits are delivered by a background worker with retries, so expect that a credit may arrive shortly after the market resolves and may be retried with the same txRef until you acknowledge it with a 200.


Reconciliation

Return a stable transactionId on every successful operation. The platform keeps a ledger keyed by txRef ↔ your transactionId, which is what both sides use to reconcile disputes and the monthly settlement.


Reference implementation

A runnable reference of this wallet lives at examples/casino-integration/ — the server-side analogue of ws-test.html. casino-wallet-stub.mjs verifies our token and implements debit/credit/rollback/balance with txRef idempotency; run-smoke.ts drives the real platform against it end to end. See the example README to run it.