Appearance
Getting started
Six steps take one module (the wallet here; player and webhook.receive follow the same track) from nothing to live. Every step ends in a Check: run it, and do not move on until it gives the answer shown.
You need a OneHazel operator account and a host that can serve HTTPS on a public address. New to OneHazel? Start here walks the whole path, from creating the account to a game live on your casino.
Connect sits behind a feature switch that OneHazel turns on: until it is on, every connect REST route answers CONNECT_DISABLED with HTTP 403 and every connect_* MCP tool answers CONNECT_DISABLED, so ask for access through the chat button in the OneHazel app.
1. Pick a sample and run it locally
Open the sample for your stack and module, copy the code, and start it with throwaway values:
| Stack | wallet | player | webhook.receive |
|---|---|---|---|
| TypeScript + Express | sample | sample | sample |
| TypeScript + Fastify | sample | sample | sample |
| PHP (no framework) | sample | sample | sample |
| PHP + Laravel | sample | sample | sample |
| Python + FastAPI | sample | sample | sample |
bash
ONEHAZEL_CALLBACK_KEY=local ONEHAZEL_SIGNING_SECRET=local npx tsx onehazel-wallet.tsCheck: an unsigned call is refused with the contract's error envelope.
bash
curl -s -X POST http://localhost:3000/onehazel/v1/session/balance -H 'Authorization: Bearer local' -d '{}'
# {"code":"signature_invalid","message":"bad or stale X-OneHazel-Signature","retryable":false}2. Connect it to your real wallet
The sample keeps two ledgers: test (seeded with a funded test player) and live (empty). Replace the live ledger with calls into your wallet, and move the idempotency store into your database so it survives restarts. Leave the test ledger as it is: every conformance run sends X-OneHazel-Env: test and must never touch a real-money balance.
Check: a signed test call still answers from the test ledger. signForTest in @onehazel/connect (or the same function in the PHP and Python samples) builds the header:
bash
BODY='{"player_external_id":"test-player-1","provider_slug":"demo","provider_game_code":"g1","currency":"EUR","language":"en","client_type":"DESKTOP","idempotency_key":"ik_check_1"}'
SIG=$(node -e "const c=require('node:crypto');const t=Math.floor(Date.now()/1000);console.log('t='+t+',v1='+c.createHmac('sha256','local').update(t+'.'+process.argv[1]).digest('hex'))" "$BODY")
curl -s -X POST http://localhost:3000/onehazel/v1/session/start -H 'Authorization: Bearer local' \
-H "X-OneHazel-Signature: $SIG" -H 'X-OneHazel-Env: test' -H 'Idempotency-Key: ik_check_1' -d "$BODY"
# {"launch_token":{"token":"lt_..."},"player":{"external_id":"test-player-1","currency":"EUR","balance_minor":100000}}3. Put it on a public HTTPS address
Deploy the module where OneHazel can reach it. The base URL must be https, resolve to a public address, and carry no credentials. Paths sit under it exactly as in the contract (<base>/session/start, <base>/wallet/bet, ...).
Check: from a machine outside your network, the unsigned call from step 1 returns the same signature_invalid envelope from your public base URL.
4. Register the endpoint
Register the base URL for the module. OneHazel answers with two values once: the signing secret (whsec_...) and the callback key it will send as the bearer. Store both in your secret manager; neither is shown again. Registering the same module again later keeps both and returns neither (secretUnchanged: true), so an address or test-player fix needs no restart. If your casino mints its own provider keys, add an optional callbackKey (32 to 256 printable characters, no spaces) and OneHazel sends that as the bearer instead of minting one; registering again without it keeps the key you gave.
- In the app: Connect casino, then Integrate with AI, then register the module.
- REST, with your signed-in session token:
POST https://api.onehazel.com/onehazel-api/operators/me/connect/endpointswith{ "module": "wallet", "baseUrl": "https://wallet.example.com/onehazel/v1", "testPlayerId": "test-player-1", "testGame": { "provider_slug": "acme-studio", "game_code": "acme-slot" } }. Name one active game in your catalogue that may be used for test rounds (provider slug and game code as your PAM knows them) and register it with the wallet as testGame: { provider_slug, game_code }. Conformance andconnect_simulatestart every test session with it. - MCP: the
connect_register_endpointtool (Connect with MCP).
For the player module, also send testPlayerId and restrictedPlayerId: a test player in your test ledger with an active restriction (self-exclusion, a block, a limit), at most 128 characters. The lookup.restricted_flag scenario looks that player up and expects the restriction in restrictions. Without both fields a player run ends errored with reason missing_input and names the field that is missing; connect_status lists it under the player module's requiredInputs until it is set. Registering again without them clears them. Example: { "module": "player", "baseUrl": "https://player.example.com/onehazel/v1", "testPlayerId": "test-player-1", "restrictedPlayerId": "test-player-restricted" }.
Restart your module with ONEHAZEL_CALLBACK_KEY and ONEHAZEL_SIGNING_SECRET set to the two values. To rotate later, call the rotate route or connect_rotate_secret: the old secret stays valid for one hour, so set it as ONEHAZEL_SIGNING_SECRET_PREVIOUS for that hour.
Check: connect_status (or the module chip in the app) shows the module as registered.
5. Run conformance
Ask OneHazel to run the conformance scenarios for the module against your registered URL (app button, REST, or connect_run_conformance). Every call is signed, carries X-OneHazel-Env: test, and targets your test player only.
Check: the run finishes with every blocker scenario passing and the module shows conformant. If a scenario fails, connect_conformance_evidence shows the exact request and your response (secrets masked); fix and run again.
6. Go live
Activate the Marketplace vendor you want. When every module it requires is conformant, the connection is created inactive and the app shows Go live. A person on your team clicks it; no API, tool or agent can do that step.
Check: the vendor's connection shows Active in Connections. A demo launch (POST /connect/v1/demo-launch, or demoLaunch in the helper) returns a game_url only for a connector whose listing carries a launch template; otherwise it answers 404 game_unknown and your casino builds the game link from its own catalogue (see who calls whom).