Appearance
For the operator's engineer building the wallet module.
wallet
The wallet module is your seamless wallet, exposed once in the shape of the contract. Every game studio in the Marketplace reaches your balances through it: OneHazel translates each studio's protocol into these calls, so you never build a per-studio wallet.
Who calls whom
OneHazel calls you. You host the operations under the base URL you register for wallet.
| Operation | What it does | Idempotency key |
|---|---|---|
POST /session/start | Mint a single-use launch token for a player and game | yes |
POST /session/exchange | Exchange the launch token for a wallet-capable session token | yes |
POST /session/balance | Read the live balance for a session | no |
POST /wallet/bet | Debit a real-money stake | yes |
POST /wallet/win | Credit a win | yes |
POST /wallet/rollback | Reverse a prior bet or win by the amount you stored | yes |
POST /freerounds/issue, /freerounds/cancel, /freerounds/settle | Record, cancel and lump-settle a free-round grant | yes |
POST /freerounds/get | Read a grant's current state | no |
The full request and response shapes are in the contract and the OpenAPI document at /openapi/connect-v1.yaml.
Headers and signature
Every call carries three headers. Check them in this order, over the raw request body, before you parse it.
| Header | Value | Reject with |
|---|---|---|
Authorization | Bearer <callback key>, the cck_ value shown once at registration, or the callbackKey you supplied | 401 unauthorized |
X-OneHazel-Signature | t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of "<t>." + raw body keyed by your whsec_ signing secret | 401 signature_invalid when the MAC does not match or t is more than 300 s from your clock |
X-OneHazel-Env | test or live | an error when it is missing; route test to your test player or sandbox ledger only |
Compare the MAC in constant time. During a secret rotation accept both the new and the previous secret for one hour.
This is deliberately a different format from webhook.receive, which keeps sha256=<hex> with no bearer and no timestamp.
Money and idempotency
- Amounts are integer minor units (
amount_minor,balance_minor) at the currency's exponent:100is 1.00 EUR but 100 JPY. A code outside the contract's table is400 invalid_requestnaming the code; a listed currency that is not the player's is422 currency_mismatch. Never round. See Currencies and exponents. - Every money-moving call carries the same key in the
Idempotency-Keyheader and theidempotency_keybody field. A replay returns the original response withduplicate: true; the same key with a different body is409 idempotency_key_conflict. - Free-round bets never debit, so OneHazel never sends
wallet/betfor them.freerounds/settlecarries the lump credit itself and is never paired with awallet/winfor the same grant.
Registration
Register the base URL with a testPlayerId: a funded player in your test ledger. Wallet registration is refused without one, because conformance moves test money against that player and nobody else.
Conformance
A run sends every call signed, with X-OneHazel-Env: test, against your test player only.
| Scenario | Severity |
|---|---|
auth.bad_bearer | blocker |
auth.bad_signature | blocker |
auth.stale_timestamp | blocker |
balance.ok | blocker |
bet.ok | blocker |
win.ok | blocker |
bet.insufficient_funds | blocker |
bet.duplicate_returns_original | blocker |
rollback.bet | blocker |
rollback.win | blocker |
rollback.amount_mismatch | major |
freerounds.bet_no_debit | major |
freerounds.settle_lump | major |
env.test_header_required | blocker |
shape.empty_body_is_failure | blocker |
What passing looks like for each one is on Conformance. The module is conformant when the latest finished run passed.
Samples
| Stack | Sample |
|---|---|
| TypeScript + Express | wallet |
| TypeScript + Fastify | wallet |
| Python + FastAPI | wallet |
| PHP (no framework) | wallet |
| PHP + Laravel | wallet |
See Samples for how to run one locally.