# Operator wallet contract OneHazel connects game studios to your wallet. Each studio speaks its own wallet protocol, and OneHazel translates every one of them into one contract that you implement once. You stay the system of record for player balances; OneHazel never holds player funds. **Machine-readable spec:** [operator-wallet.openapi.yaml](/specs/operator-wallet.openapi.yaml) (OpenAPI 3.1, no key needed). ## The endpoints you implement OneHazel calls these on your host, under the base path `/provider/v1`. Every call is an HTTPS `POST` with a JSON body. | Endpoint | Purpose | |---|---| | `/session/start` | Mint a single-use launch token for a player and game | | `/session/exchange` | Exchange a launch token for a session token and return the player view | | `/session/balance` | Return the player's current balance | | `/wallet/bet` | Debit a stake | | `/wallet/win` | Credit a win | | `/wallet/rollback` | Reverse an earlier bet or win | ## Authentication Every call carries `Authorization: Bearer `. You issue the key; OneHazel stores it encrypted against the connection. There is no request-body signature on these calls, so integrity relies on TLS and the key. OneHazel can send the calls from a fixed egress address so you can allowlist it. ## Money Every amount and balance is an **integer number of minor units** of the player's currency: `1050` is 10.50 in a two-decimal currency. OneHazel converts each studio's own money format to minor units before calling you, so you never see a decimal amount. ## Idempotency and replay Every mutating call carries an `idempotency_key`: - Keys are opaque, unique across bets, wins and rollbacks, and stable across a studio's retries of the same transaction. - A rollback has its own key and names the transaction it reverses in `target_idempotency_key`. - When a key has already been processed, do not move money again. Return the original result with `duplicate: true`. - That same rule is what makes two simultaneous copies of one transaction safe: the second one sees the first and answers `duplicate: true`. ## Wallet responses A wallet call returns the balance after the call and a `status`: | `status` | Meaning | |---|---| | `accepted` | The bet or win was applied | | `insufficient_funds` | The stake exceeds the balance; nothing moved (HTTP 402 is also read this way) | | `rolled_back` | The rollback was applied | A replay keeps the original status and adds `duplicate: true`. OneHazel reads a response in this order: `duplicate`, then insufficient funds, then `accepted` or `rolled_back` with an HTTP status below 400. Anything else counts as a failed call. Errors use `{ "success": false, "error": { "code": "...", "message": "..." } }`. OneHazel branches on `error.code`, not on the HTTP status alone, because one status can carry different meanings: | Code | HTTP | When | |---|---|---| | `AUTH_INVALID` | 401 | The integration key is wrong | | `launch_token_unknown` | 401 | The launch token was never issued | | `launch_token_expired` | 401 | The launch token passed its lifetime | | `launch_token_consumed` | 409 | The launch token was already exchanged | | `session_unknown` | 401 | The session token is not recognised | | `session_expired` | 401 | The session token has expired | ## Behaviour worth knowing - **No zero-amount wins.** A losing spin's empty win or a round-close acknowledgement is recorded on the OneHazel side and acknowledged to the studio without calling you, so you may reject `amount_minor: 0`. - **Free-round stakes are not debited.** A free-round bet never reaches `/wallet/bet`; a win from a free round arrives as a normal `/wallet/win`. - **Rollback carries the original amount.** Studios often omit the amount on a cancel, so OneHazel looks it up and forwards it as `metadata.amount_minor`. Derive the direction from the target: a reversed bet is a credit, a reversed win is a debit. - **Rounds.** `round_external_id` is the studio's round id; where a studio has no round concept, OneHazel uses the transaction id. `round_closed: true` on a win marks the round as finished. ## Known limitations today - **Two-decimal currencies only.** Zero- and three-decimal currencies are rejected rather than converted. - **No unified launch endpoint.** Each studio is launched through its own route today; there is no single OneHazel launch call that covers every studio. - **Demo mode on one studio only.** Play without a real wallet session is available for one studio, not across the catalogue. - **Jackpot data is not forwarded.** Jackpot contributions and jackpot-win flags that a studio reports are not passed through to your wallet; you receive only the stake and the win amount. - **Free-round issuance on one studio only.** Issuing free rounds through OneHazel is supported for one studio. For the others, free-round play is handled as described above, but you cannot issue grants through OneHazel.