--- title: "Connect: samples guide" description: What each generated Operator Contract sample contains, how to run it locally, and how to point conformance at it. --- For the operator's engineer starting a module from a generated sample. # Samples guide Every sample is a complete, runnable module for one stack and one module, generated from the Operator Contract. Start from one, replace its stores with your own, and it already does the parts that are easy to get wrong. ::: warning Generated, do not edit here The sample pages are generated from templates and the contract. They are not edited by hand in this repository, and an edit to a sample page is overwritten the next time they are generated. Copy a sample into your own repository and change it there. ::: ## What each sample contains | Stack | wallet | player | webhook.receive | payments.intent | |---|---|---|---|---| | TypeScript + Express | [wallet](/connect/samples/typescript/express/wallet) | [player](/connect/samples/typescript/express/player) | [webhook.receive](/connect/samples/typescript/express/webhook.receive) | [payments.intent](/connect/samples/typescript/express/payments.intent) | | TypeScript + Fastify | [wallet](/connect/samples/typescript/fastify/wallet) | [player](/connect/samples/typescript/fastify/player) | [webhook.receive](/connect/samples/typescript/fastify/webhook.receive) | [payments.intent](/connect/samples/typescript/fastify/payments.intent) | | Python + FastAPI | [wallet](/connect/samples/python/fastapi/wallet) | [player](/connect/samples/python/fastapi/player) | [webhook.receive](/connect/samples/python/fastapi/webhook.receive) | [payments.intent](/connect/samples/python/fastapi/payments.intent) | | PHP (no framework) | [wallet](/connect/samples/php/plain/wallet) | [player](/connect/samples/php/plain/player) | [webhook.receive](/connect/samples/php/plain/webhook.receive) | [payments.intent](/connect/samples/php/plain/payments.intent) | | PHP + Laravel | [wallet](/connect/samples/php/laravel/wallet) | [player](/connect/samples/php/laravel/player) | [webhook.receive](/connect/samples/php/laravel/webhook.receive) | [payments.intent](/connect/samples/php/laravel/payments.intent) | Each wallet and player sample: - checks the `Authorization: Bearer` callback key, then verifies `X-OneHazel-Signature` (`t=,v1=`, 300 s tolerance, constant-time compare) over the raw body before parsing, then reads `X-OneHazel-Env`; - keeps a separate `test` ledger seeded with a funded test player, so conformance traffic never touches the `live` one; - answers errors with the flat `{ code, message, retryable }` envelope and handles money as integer minor units; - for wallet, keeps an idempotency store that returns the original response with `duplicate: true` on a replay; - for player, also seeds a restricted test player. Each webhook.receive sample verifies `X-OneHazel-Signature: sha256=` over the raw body, deduplicates on `deliveryId`, and acknowledges before doing slow work. Each payments.intent sample is the payment status receiver: the same `sha256=` check, then a required `X-OneHazel-Env`, deduplication on `deliveryId`, and a settled deposit credited once per `intent_id`. Creating intents is a call your cashier makes to OneHazel, so no sample serves it. The same bytes are served to a coding agent by the `connect_samples` MCP tool, so a person and an agent start from the same code. ## Run one locally Each sample page has a **Run it** section with the exact commands for its stack. The pattern is the same everywhere: install the framework, then start the module with the two values you were shown at registration. For a first local run, throwaway values are enough: ```bash ONEHAZEL_CALLBACK_KEY=local ONEHAZEL_SIGNING_SECRET=local npx tsx onehazel-wallet.ts ``` | Variable | Used by | Meaning | |---|---|---| | `ONEHAZEL_CALLBACK_KEY` | wallet, player | the callback key shown once at registration | | `ONEHAZEL_SIGNING_SECRET` | all | the signing secret shown at registration | | `ONEHAZEL_SIGNING_SECRET_PREVIOUS` | all | the old secret, during the one-hour overlap after a rotation | | `ONEHAZEL_TEST_PLAYER_ID` | wallet, player | the test player id, default `test-player-1` | | `ONEHAZEL_RESTRICTED_TEST_PLAYER_ID` | player | the restricted test player id, default `test-player-restricted` | | `ONEHAZEL_BASE_PATH` | TypeScript, Python, plain PHP | changes the default `/onehazel/v1` path | The Laravel samples use routes under `/api/onehazel/` instead of a base path variable. Each **Run it** section names the base URL to register. To check a local build before asking OneHazel to run, sign requests with `signForTest` (or `signWebhookForTest`) from [`@onehazel/connect`](/connect/contract#helper) and replay the scenarios on [Conformance](/connect/conformance). Step 2 of [Getting started](/connect/getting-started) shows a signed call with curl. ## Point conformance at it OneHazel runs conformance from its own servers, so the module must be reachable on a public https address. A `localhost` or private address is refused at registration. 1. Expose the module on a public https URL: deploy it, or put your local server behind an https tunnel. 2. Register that base URL for the module (`connect_register_endpoint`, or `POST /operators/me/connect/endpoints`). For wallet include `testPlayerId` and `testGame` (any `provider_slug` / `game_code`: the samples accept every game); for player include `testPlayerId` and `restrictedPlayerId`, matching the ids the sample seeds. 3. Restart the sample with the callback key and signing secret from the registration response. 4. Start a run (`connect_run_conformance`, or `POST /operators/me/connect/conformance`) and read the result with `connect_conformance_evidence`. ## Before you go live - Replace the in-memory or file stores with your database. The idempotency store must survive restarts. - Point the `live` ledger at your real wallet and keep `test` traffic on the test ledger. - Keep the callback key and signing secret in your secret manager, never in the repository.