--- title: "Connect: troubleshooting" description: Symptom, cause and fix for the Connect failures engineers hit most, from a disabled feature to a failed signature scenario. --- 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/`) 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](/reference/errors). ## Access and keys | Symptom | Cause | Fix | |---|---|---| | Every `/operators/me/connect/*` REST call answers HTTP 403 `CONNECT_DISABLED`, and every `connect_*` tool answers JSON-RPC error `-32001` with `CONNECT_DISABLED` | Connect 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_*` tools | The 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_` key | These 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 2 | Exit 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 | Symptom | Cause | Fix | |---|---|---| | `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_found` | `session/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_rejected` | Your 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_REGISTRABLE` | Only `wallet`, `player` and `webhook.receive` take a registered endpoint. | The other modules are derived; see their [module pages](/connect/modules/launch). | ## Running conformance | Symptom | Cause | Fix | |---|---|---| | `CONNECT_MODULE_NOT_REGISTERED` when starting a run | No endpoint is registered for that module. | Register it first. | | `RATE_LIMITED` when starting a run | At most 10 conformance runs per operator per hour. | Wait, and use the [samples](/connect/samples) and `signForTest` to check locally between runs. | | A player run ends `errored` with reason `missing_input`, naming `restrictedPlayerId` or `testPlayerId` | The 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` array | The 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_blocked` | The 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 response | OneHazel never follows redirects, so the redirect itself is recorded as your answer. | Serve the contract paths directly at the registered base URL. | ## Signature scenarios | Symptom | Cause | Fix | |---|---|---| | `auth.stale_timestamp` fails, or real calls are rejected as stale | Your 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` fails | Your 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 server | The 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 rotation | The 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 round | The two formats differ. Wallet and player use `t=,v1=` over `"." + raw body` plus a bearer; webhook.receive uses `sha256=` 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 | Symptom | Cause | Fix | |---|---|---| | `env.test_header_required` fails | One 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` header | Live 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 | Symptom | Cause | Fix | |---|---|---| | The chip or `connect_status` still shows the old state after a new run started | Not 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: derived` | No 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](/connect/modules/launch) for each rule. | | Calls signed with the old secret start failing an hour after `connect_rotate_secret` | A 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 key | Registering 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`. |