Skip to main content
In live mode your game server calls your wallet to take stakes and pay wins. Your wallet stays the only place a player’s balance changes, including for your other products. The game server never holds funds. You implement three endpoints at the wallet URL you set under Mode in Settings, for example https://wallet.luckystar.example/kinematic/: The Wallet API reference has every field. This page explains the rules your implementation must keep. In the dashboard, the Connect your wallet step on Integration sums up the three calls with your wallet URL, and the page’s Wallet contract tab has the contract to read.
Sandbox casinos need no wallet: the game server keeps test balances. Build and test your wallet against a sandbox casino first, then switch to live. See Go live.

Signatures

Every call is a POST with a JSON body and these headers: Check every call before you act on it:
  1. Compute the HMAC over the exact bytes you received, before you parse them, and compare it with the header in constant time.
  2. Refuse a timestamp more than 60 seconds from your clock.
  3. Check that Idempotency-Key equals the body’s id, and that the path’s endpoint matches the body’s kind.
Refuse a call that fails any check with a status other than 200, such as 401. A retry of the same call arrives with a fresh timestamp and signature.
verify.mjs
Serve the endpoints over https, and answer them directly. The game server never follows a redirect: it treats one as a failed call and retries.

Receipts

Answer every call you accept with HTTP 200 and a receipt:
  • id and currency repeat the request’s.
  • status is applied, or declined for a debit the player cannot cover. Credits and balance reads are always applied.
  • balance is the player’s balance after the operation, in the currency’s minor units.
  • revision is your wallet-wide revision after the operation. It increases with every change to the account, including deposits, withdrawals and your other products. Two different balances never share a revision.
  • amount, balance and revision are canonical integer strings: digits only, no decimals, no signs, no leading zeros.
  • The receipt is a JSON object of at most 16 KiB.

Idempotency

Implement atomic, persistent idempotency, not only the endpoints:
  • Store each ID with a fingerprint of its request and the exact receipt you answered, in the same database transaction as the balance change.
  • A repeated ID with the same body gets the original receipt, even if the balance has changed since.
  • A repeated ID with a different body is a conflict. Refuse it with a status other than 200, such as 409. The game server never sends one.
  • Never expire IDs while the game server may still retry them.
The game server keeps every call it has not seen answered and sends it again, with the same ID and the same body, after 1, 2, 4 and up to 30 seconds. A timeout, an HTTP error, a redirect, a body that is not a receipt, or a receipt with another id or currency all count as unanswered. None of them counts as a declined debit, so a plain 400 never releases a bet.

Debits

  • A debit can pay for several bets at once: rounds lists their bet IDs, and their stakes add up to amount.
  • Apply the whole amount, or durably record status: "declined" and apply nothing. Never debit part of a batch. A declined ID stays declined if funds arrive later.
  • Lock and check the same account that your other products spend from.
  • Record which rounds each applied debit named.
  • A Blackjack double, split or insurance arrives as its own debit with an ID that starts with stake_. Its rounds holds the round’s bet ID and amount is the extra stake.
No outcome exists when a debit arrives. The bet carries only the hash of a seed that stays in the player’s browser until your debit has applied and the game server has signed its acceptance. So nobody can know a bet’s outcome before your wallet takes the stake, and no bet is refused because it would win. A declined debit refuses its bets with insufficient_funds. No money moved.

Credits

  • The game server credits each round that pays on its own, as soon as the round ends. A losing round has no credit.
  • A credit is the gross payout: it includes the returned stake, because the debit took the whole stake.
  • Never decline a credit for funds. Credit availability is your liability. Answer applied.
  • Each credit carries one settlement per round: the player’s signed bet, the revealed seed, the VRF proof and output, and the round’s steps. A refund carries refund instead.
  • A payout of a bet at a return to player other than 99% also carries manifest, the payload of the signed math manifest the bet was placed under (about 7 KB). Its SHA-256 is the math the player’s authorization signs, and it names the house edge the round paid at.

Balance reads

Answer the player’s current balance and your current revision. The game server reads a player’s balance at most once every 5 seconds, however many tabs the player has open. Every debit and credit receipt already carries the new balance.

Several currencies

Every call names its currency, and a player’s currencies are separate accounts. Answer with that currency’s balance in its minor units, under the same currency. One revision counter across a player’s currencies works if it increases with every change it reports.

Balance changes outside the games

When a player deposits, withdraws or spends in another product, tell the game server. It reads the balance again after any settlement in progress and pushes it to every tab the player has open:
wallet-events.mjs
The hint is never a balance itself: the game server always reads your /balance. Without hints, an open game refreshes its balance when the page becomes visible and every 30 seconds while it stays idle.

A reference wallet

This server keeps the contract with balances and receipts in memory. Use it to compare your implementation’s answers, never as storage:
reference-wallet.mjs

Checking credits before they apply

Every payout credit carries what your wallet needs to check it: the player’s own signed bet, the seed it committed to, and the VRF proof under your casino’s key. A wallet that checks a credit applies no payout the engine does not compute from the proof of a round you debited. The check is optional. Kinematic’s verifier library implements it as checkCredit; write to hello@kinematic.run to get it. For each settlement, the check confirms, in order: Your game server publishes its VRF keys, retired ones included, at GET <game server>/v1/catalog under vrf.keys. A bet always settles under the key its acceptance named, even after a rotation. Refuse a credit that fails the check with HTTP 409 and change nothing:
The game server keeps the credit under the same ID and body, retries it, and the round stays owed. A refusal means your wallet and your game server disagree about a payout: find out why before anything else, and never edit or drop the credit. The check proves that a payout is the outcome of a round you debited. It does not prove that every winning round was credited: a losing round has no credit, and a winning round’s credit can be late. It does not check an abandoned refund beyond its amount.