---
title: "Connect: getting started"
description: Six steps from nothing to a conformant Operator Contract module, each ending in a check you can run.
---
# 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](/connect/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](/connect/samples/typescript/express/wallet) | [sample](/connect/samples/typescript/express/player) | [sample](/connect/samples/typescript/express/webhook.receive) |
| TypeScript + Fastify | [sample](/connect/samples/typescript/fastify/wallet) | [sample](/connect/samples/typescript/fastify/player) | [sample](/connect/samples/typescript/fastify/webhook.receive) |
| PHP (no framework) | [sample](/connect/samples/php/plain/wallet) | [sample](/connect/samples/php/plain/player) | [sample](/connect/samples/php/plain/webhook.receive) |
| PHP + Laravel | [sample](/connect/samples/php/laravel/wallet) | [sample](/connect/samples/php/laravel/player) | [sample](/connect/samples/php/laravel/webhook.receive) |
| Python + FastAPI | [sample](/connect/samples/python/fastapi/wallet) | [sample](/connect/samples/python/fastapi/player) | [sample](/connect/samples/python/fastapi/webhook.receive) |
```bash
ONEHAZEL_CALLBACK_KEY=local ONEHAZEL_SIGNING_SECRET=local npx tsx onehazel-wallet.ts
```
**Check:** 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`](/connect/contract#helper) (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 (`/session/start`, `/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/endpoints` with `{ "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 and `connect_simulate` start every test session with it.
- MCP: the `connect_register_endpoint` tool ([Connect with MCP](/connect/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](/connect/conformance) 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](/connect/modules/launch#who-calls-whom)).
## See also
- [Module guides](/connect/modules/wallet), [Samples guide](/connect/samples), [Troubleshooting](/connect/troubleshooting) and [FAQ](/connect/faq).