Skip to content
For LLMsView as Markdown·

For the operator's engineer debugging a Connect setup or a failed conformance run.

Troubleshooting ​

Start with connect_status (or GET /operators/me/connect/status): each module carries a blocking line that says what to do next. For a failed run, connect_conformance_evidence (or GET /operators/me/connect/conformance/<runId>) shows every request OneHazel sent and every response you returned, with secrets masked.

Platform error codes below are listed with their meaning on the error reference.

Access and keys ​

SymptomCauseFix
Every /operators/me/connect/* REST call answers HTTP 403 CONNECT_DISABLED, and every connect_* tool answers JSON-RPC error -32001 with CONNECT_DISABLEDConnect is switched off for this environment. It is one switch for both surfaces.Ask for access through the chat button in the OneHazel app. After it is switched on, allow up to 30 seconds before retrying.
A connect_* tool answers -32001 with MCP_CONNECT_NOT_ENABLED, and tools/list shows no connect_* toolsThe API key does not carry the Connect scope. Keys created on the API Keys page never have it.This key has no Connect scope. Create one in app.onehazel.com under Connect casino, Integrate with AI (the API Keys page cannot add it).
A REST call to /operators/me/connect/* answers 401 AUTH_MISSING with an oh_live_ keyThese routes accept only a signed-in session token, not an API key.Call them with your session's bearer token, or use the MCP tools with the API key.
The stdio bridge connects but every call fails, or the bridge exits with code 2Exit 2 means ONEHAZEL_API_KEY is not set. Otherwise the bridge may be forwarding to an address that does not answer.Set ONEHAZEL_API_KEY. To pin the server address, set ONEHAZEL_MCP_URL=https://api.onehazel.com/mcp-server. You need that override only on @onehazel/mcp 0.1.0 installs, because 0.1.1 and later default to /mcp-server.

Registering an endpoint ​

SymptomCauseFix
CONNECT_INVALID_URL with "baseUrl must use https"The base URL is http://. OneHazel sends the callback key and signed bodies to it, so plain http is refused.Serve the module over https and register the https:// URL.
CONNECT_INVALID_URL with "URL points at a private or internal address", "baseUrl does not resolve to a public address" or "URLs with embedded credentials are not allowed"The SSRF guard refused the URL: a private or loopback IP, a localhost-style or internal name, a host that resolves to a private address or does not resolve at all, or user:pass@ in the URL.Register a public hostname that resolves to a public IP, with no credentials in the URL. For local testing, put the sample behind a public https tunnel.
VALIDATION_ERROR "wallet registration requires testPlayerId"The wallet module must name its test player.Register again with testPlayerId set to a funded player in your test ledger.
VALIDATION_ERROR "player registration requires testPlayerId" or "player registration requires restrictedPlayerId"The player module must name both test players. Registering again keeps stored values, so this means the field was never set or was cleared with null.Register again with the missing field: testPlayerId (a player in your test ledger) or restrictedPlayerId (a test player with an active restriction).
VALIDATION_ERROR "wallet registration requires testGame"The wallet module must name its test game.Register again with testGame: { provider_slug, game_code }, one active game in your catalogue that test sessions may use.
Conformance run errored with reason wallet_route_not_foundsession/start answered 404 with no contract error body, so nothing at your registered URL speaks the contract.Check the registered base URL (it must be the full prefix the contract paths follow), fix it with connect_register_endpoint, then run conformance again.
Conformance run errored with reason test_game_rejectedYour PAM refused the declared test game at session/start. The detail reads "The game you declared was rejected by your casino, check provider_slug and game_code." plus your status and code.Register the wallet again with a testGame your PAM accepts under X-OneHazel-Env: test, then run conformance again.
CONNECT_MODULE_NOT_REGISTRABLEOnly wallet, player and webhook.receive take a registered endpoint.The other modules are derived; see their module pages.

Running conformance ​

SymptomCauseFix
CONNECT_MODULE_NOT_REGISTERED when starting a runNo endpoint is registered for that module.Register it first.
RATE_LIMITED when starting a runAt most 10 conformance runs per operator per hour.Wait, and use the samples and signForTest to check locally between runs.
A player run ends errored with reason missing_input, naming restrictedPlayerId or testPlayerIdThe player module needs both a test player and a restricted test player, and one is not stored. connect_status lists it under requiredInputs.Register the player endpoint again with both testPlayerId and restrictedPlayerId (at most 128 characters), then run again.
lookup.restricted_flag fails with an empty restrictions arrayThe player named as restrictedPlayerId has no active restriction in your test ledger, or the lookup ignores it.Give that test player an active restriction (a limit, a block or a self-exclusion) and return it in restrictions.
A run ends errored with reason ssrf_blockedThe base URL was re-checked when the run started, or on a later call, and now points at a private address or no longer resolves.Make the host resolve only to public addresses.
A scenario fails with a 301, 302 or other redirect as your responseOneHazel never follows redirects, so the redirect itself is recorded as your answer.Serve the contract paths directly at the registered base URL.

Signature scenarios ​

SymptomCauseFix
auth.stale_timestamp fails, or real calls are rejected as staleYour check accepts timestamps outside 300 s, or your server clock has drifted. The scenario sends a correct MAC with a timestamp 600 s old.Reject any t more than 300 s from your clock, and keep the clock synced with NTP.
auth.bad_signature or deliver.tampered_rejected failsYour server accepts a body whose MAC does not match.Compute the HMAC over the raw bytes and reject on mismatch, comparing in constant time.
Every signed call is rejected by your own serverThe MAC is computed over a re-serialised body. Parsing JSON and stringifying it again changes spacing and key order, so the MAC never matches.Capture the raw request bytes before any body parser runs and verify those. Each sample shows how for its framework.
Every signed call is rejected after a rotationThe module still holds the old secret, or holds only the new one while a run queued earlier signs with the old one.Set the new secret, and keep the old one as the previous secret for an hour (see below).
Wallet calls rejected, webhook deliveries fine, or the other way roundThe two formats differ. Wallet and player use t=<ts>,v1=<hex> over "<t>." + raw body plus a bearer; webhook.receive uses sha256=<hex> over the raw body with no bearer and no timestamp.Use the verifier for the right module: verifySignature for wallet and player, verifyWebhookSignature for webhooks.

Environment header ​

SymptomCauseFix
env.test_header_required failsOne of: your server accepts a call with no X-OneHazel-Env, or a test call resolves to a player other than the testPlayerId you registered.Reject calls without the header, and route test traffic to the test ledger where the registered test player lives.
A webhook delivery is rejected for its X-OneHazel-Env headerLive deliveries carry no X-OneHazel-Env; conformance deliveries carry X-OneHazel-Env: test.Do not require or reject the header on webhook.receive.

Status and secrets ​

SymptomCauseFix
The chip or connect_status still shows the old state after a new run startedNot a defect. Only a finished run carries a verdict, so a queued or running run never replaces the previous result. The app's status card also refreshes every 30 seconds while the tab is open.Wait for the run to finish. connect_status reads fresh on every call; the app updates on its next refresh or when you return to the tab.
A module shows conformant but evidence: derivedNo conformance run backs that module; its state comes from a rule. In v1 that is launch, widget, attribution and events.push.Nothing to fix. See the module pages for each rule.
Calls signed with the old secret start failing an hour after connect_rotate_secretA rotation keeps the previous secret valid for one hour, then only the new one verifies.Deploy the new secret within the hour. During the overlap set it as the current secret and the old one as ONEHAZEL_SIGNING_SECRET_PREVIOUS in the samples, then drop the old one.
Registering an endpoint again returned no signing secret and no callback keyRegistering a module that already exists keeps both: the response says secretUnchanged: true and changes only the base URLs, test players and test game.Nothing to deploy and no restart: keep the values you already stored. To replace them, use the rotate route or connect_rotate_secret.