Skip to content

Seamless wallet integration for a sportsbook: debit, credit, rollback

How a sportsbook seamless wallet works: signed debit, credit and rollback calls, idempotency, retries and reconciliation, for a play-for-fun sportsbook.

Updated 10 min readBy The Sportsbook team

A sportsbook seamless wallet keeps each player's token balance in your own system: the sportsbook never stores it, and instead calls your server to debit a stake when a bet is placed and to credit or roll back tokens when the bet settles. With The Sportsbook that means three signed HTTPS endpoints you host (debit, credit, rollback), each one idempotent, answering within 5 seconds, and the balance is play-for-fun tokens with no monetary value, never real money. This guide covers when to choose it over the managed balance, how to verify the HMAC signature, how retries and rollbacks behave, and how to reconcile.

Key takeaways

Managed balance or a sportsbook seamless wallet: which to choose

Start with the question most teams skip: does a token balance already exist somewhere else on your platform, used by other features? If it doesn't, you almost certainly want the managed mode. If it does, and players would be confused by two numbers, seamless is worth the work.

In managed mode you add tokens with a credit call and the ledger does the rest:

Shell
curl -X POST https://api.thesportsbook.app/b2b/v1/users/viewer-42/credit \  -H "Authorization: Bearer $SPORTSBOOK_API_KEY" -H "Content-Type: application/json" \  -d '{"amount": 50, "idempotency_key": "credit-8f1c2", "reason": "Daily free tokens"}'

The rest of this guide is about seamless. If you're still designing where tokens come from, read how to design a free token economy first; the wallet mode is a detail compared with the rules for getting tokens.

One rule applies to both modes and matters more in seamless: the balance you connect must be a separate, free token balance. It can't be linked, directly or through a conversion, to money, e-money, crypto-assets, a real-money betting account, or loyalty points that can be redeemed for anything (AUP 3.3). If your app has a purchasable soft currency, it can't be the sportsbook balance.

The three calls we send: debit, credit and rollback

Your wallet has a base URL, and we POST to three paths under it:

TEXT
POST {wallet_base_url}/debitPOST {wallet_base_url}/creditPOST {wallet_base_url}/rollback

Each body is the same shape. A placement debit looks like this:

HTTP
POST /wallet/debit HTTP/1.1Content-Type: application/jsonX-Timestamp: 1790000000X-Signature: 5d1f0c…(64 hex chars){"user_ref":"viewer-42","amount":10,"currency":"TOKENS","bet_id":"0c9a8d5e-…","idempotency_key":"0c9a8d5e-…:debit","op":"debit","reason":"Bet placed — 1X2 / 1","timestamp":1790000000}

Which call arrives when follows from the outcome of the round:

Singles and accumulators follow the same table. An accumulator is one round with one debit and one settlement, however many legs it has.

Your answers are simple. 2xx with {"ok": true, "balance": 130, "ref": "tx_91822"} accepts the movement (balance and ref are optional; ref is your transaction id and is stored with the operation). 2xx with {"ok": false, "code": "INSUFFICIENT_FUNDS"} refuses it for good, and the player sees "insufficient balance". Any other ok: false code, or a 4xx other than 408, 425 and 429, is also a definitive refusal. A 5xx, a 408/425/429, a timeout, a network error or an unreadable body means "outcome unknown", and we retry with the same key.

Verifying the signature (HMAC-SHA256)

Every request carries two headers: X-Timestamp (Unix seconds) and X-Signature. The signature is:

TEXT
X-Signature = hex( HMAC_SHA256( wallet_secret, X-Timestamp + "." + rawBody ) )

Three details decide whether your check is correct:

Every retry is re-signed with a fresh timestamp. So the signature is not a deduplication key: two copies of the same debit have different signatures and the same idempotency_key.

The signature section of the docs (opens in a new tab) has complete Node.js (Express) and PHP handlers. They're short, so copy them from there rather than from a blog post that may drift. Store the wallet secret like a password, in an environment variable or a secret manager.

Idempotency done right

The classic wallet bug in any sportsbook is a movement applied twice because a retry arrived while the first attempt was still running, or after a timeout that the receiver thought was a failure. The fix is boring and reliable:

If two copies race, the unique index makes one of them fail on insert; that one reads the stored row and returns its response. This is also what makes concurrency safe (see the limits below).

Don't key on bet_id alone. One round can produce a debit, a credit, a reversal and a second credit, each with its own key. The key formats are predictable (<bet_id>:debit, <bet_id>:credit, <bet_id>:revert:1, <bet_id>:credit:1), but treat them as opaque strings: store and compare, don't parse.

Timeouts and retries

The numbers below come from the delivery and retries section of the docs (opens in a new tab). Design your endpoint around them.

Two consequences follow. First, the 5-second timeout and the 6-second budget interact: fast failures (connection refused, a 5xx) get all three attempts, but a debit that hangs for 5 seconds leaves room for only one short retry. Keep the debit path fast, well under a second if you can, and don't call slow services from it.

Second, settlements come in bursts. When a popular match ends, many rounds settle at once and up to 8 credits hit you in parallel. Your idempotent insert has to hold up under that.

Rejected credits and rollbacks are retried too, because you're expected to accept them. After the last attempt the operation is marked failed and appears in reconciliation.

Edge cases that break naive wallets

A rollback for a debit you never saw. If a placement debit's outcome stays unknown (say it timed out on our side but reached you late), the bet isn't placed and we send rollback with key <bet_id>:rollback and reason placement_compensation. If you applied the debit, give the stake back. If you never saw it, answer ok: true anyway and remember the key, so a late copy of that debit can be refused.

Nothing reached you. If none of the attempts reached your wallet at all (connection refused, a DNS failure, a TLS certificate error), nothing was sent, nothing was charged, and no rollback follows.

A credit of 0. Lost rounds close with a credit of amount 0 and reason bet_lost. It moves no tokens. Accept it and use it to mark the round closed on your side. The console lists it as "Round closed".

Credits with no session. Settlement doesn't need the player online. Credits and rollbacks can arrive hours after the player left or after the session expired. Never tie them to a login or an active session.

Re-settlement. Occasionally a result is corrected after settlement. We then recover what the previous settlement returned with a debit, key <bet_id>:revert:<n>, reason settlement_reversal, and settle again with a new key that carries the generation: <bet_id>:credit:<n> or <bet_id>:rollback:<n>. Keys are never reused, so the second credit is not a duplicate. If you refuse the reversal debit (for example because the player already staked those tokens), we don't retry it; the unrecovered amount is deducted from that round's next settlement. Generation numbers can skip if a settlement was corrected before it was delivered.

Ordering. Operations of the same bet are delivered in order: a credit is never overtaken by its own reversal. Operations of different bets can interleave.

Blocks and maintenance during the debit. A player block, your own maintenance mode or a suspension is checked again after your debit answers. If one of them landed in that window, the bet doesn't exist and the debit you accepted is compensated with a placement_compensation rollback.

Reconciliation: the rounds API and the console

Reconciliation is where you prove that your ledger and ours tell the same story. Do it daily, and after any incident on your side.

The rounds API gives you what you need:

Shell
# A player's settled roundscurl "https://api.thesportsbook.app/b2b/v1/users/viewer-42/bets?status=settled&limit=50" \  -H "Authorization: Bearer $SPORTSBOOK_API_KEY"# Open rounds and the tokens on them (optionally for one player)curl "https://api.thesportsbook.app/b2b/v1/rounds/open?user_ref=viewer-42" \  -H "Authorization: Bearer $SPORTSBOOK_API_KEY"

For seamless, the open rounds should match the stakes you debited and haven't yet credited or rolled back. A round you debited that doesn't appear as open or settled is the first thing to investigate.

The console has two views for this. Wallet → Operations lists every call with its attempts and last error; refused placement debits show as Refused or Not sent and never raise an alert, because they moved no tokens. Wallet → Reconciliation lists only what needs a human: credits, rollbacks and round closes that used up their attempts, compensation rollbacks that used up theirs, reversals you refused that later settlements haven't recovered, and a placement debit with an unknown outcome and no compensation. The Overview also warns while credits to your endpoint are being retried. The back office guide walks through a weekly routine that includes this check.

Never move tokens because of a browser event. The embed tells your page about a bet-placed, which is useful for refreshing a balance widget, but the source of truth is the calls to your wallet. The iframe integration guide covers those events.

Moving from managed to seamless

Many operators launch on the managed balance and switch later. The switch has one precondition: the hosted ledger has to be empty. That means every player balance debited to zero through the managed debit call, and no open bet staked from it (let them settle). Until then the switch is refused with WALLET_MODE_HAS_BALANCES and the figures.

A practical order:

If a seamless sportsbook later drops back to Free, it keeps its wallet mode, so no balance is stranded.

We're built by people with seven years of experience building sportsbook software, and the wallet protocol is where that shows most: the rollback-before-debit rule, the zero-amount close and the generation keys all exist because those cases happen in production. If you want to walk through your wallet design with us before you build, request a walkthrough. For the bigger picture, the embeddable sportsbook guide explains how sessions, the iframe and balances fit together.

Frequently asked questions

What is the difference between a transfer wallet and a seamless wallet?

With a transfer wallet, tokens are moved into the sportsbook's own balance before play and moved back later. With a seamless wallet the balance stays in your system and the sportsbook calls you for every movement. The Sportsbook calls its managed mode the Hosted Token Balance and its seamless mode the Operator-Held Token Balance.

Which endpoints do I need to build for a seamless wallet?

Three POST endpoints on your server: debit, credit and rollback. Each one verifies an HMAC-SHA256 signature, applies the movement once per idempotency key and answers JSON with an ok field. There is no balance endpoint to build; the sportsbook learns the result of each movement from your answers.

What happens if my wallet endpoint is down when a player bets?

The bet isn't placed. A placement debit is tried up to three times within six seconds. If the outcome stays unknown, the sportsbook sends a rollback for the same amount so nothing stays charged. Settlement credits wait in a durable queue and are retried up to 20 times.

Do I need the seamless wallet to launch?

No. Most operators start with the managed Hosted Token Balance, which works on every plan including Free. Seamless is a Custom add-on or included in Full, and it only makes sense when your platform already keeps a free token balance that other features use.

Can I connect a seamless wallet to a loyalty or points program?

Only to a separate free token balance. The balance you connect can't be convertible to or from loyalty points, money, crypto-assets or anything that can be bought or cashed out. Tokens must stay worthless in both directions.

Written by

The Sportsbook team

The people who build The Sportsbook. We have spent seven years building sportsbook software, and we write these guides from that work.

Last updated .

Tell us about your platform. We’ll walk you through it.

How your tokens work, where your players are, what you want to offer. We’ll show you the embed, the Studio and the console.

Free plan · nothing to pay, no approval. For businesses and creators, 18+.