---
type: how-to
title: Troubleshooting
description: Common error codes from /siteverify and the widget, with concrete fixes.
---

# Troubleshooting

Error codes returned from [`/siteverify`](../api.md#post-siteverify-customer-backend--platform) and emitted by the widget, with the cause and the fix.

## `/siteverify` error codes

The response shape:

```json
{ "success": false, "error-codes": ["..."] }
```

| Code | Cause | Fix |
|---|---|---|
| `missing-input-secret` | Request body has no `secret` field, or it is empty | Make sure the env var is set on the server, and the request body includes it |
| `invalid-input-secret` | `secret` value is malformed or unknown | Verify the value starts with `cpt_sec_` and matches what the dashboard shows. If you rotated the secret, the old one stops verifying immediately |
| `missing-input-response` | Request body has no `response` field | Read the form field named `caputchin-token` and pass its value as `response` |
| `invalid-input-response` | Wrapped token is malformed (truncated, modified, or wrong shape) | Get a fresh token by re-submitting the form. Wrapped tokens are base64url-JSON — do not trim or re-encode them |
| `timeout-or-duplicate` | Token already redeemed or expired (10-minute TTL) | Each token verifies exactly once. If you saw this on a retry, you already have a verified verdict — return the cached result instead of re-calling `/siteverify` |

## Widget client-side errors

The widget emits an `error` event with a code:

```js
widget.addEventListener("error", (e) => console.log(e.detail.code));
```

| Code | Recoverable | Cause | Fix |
|---|---|---|---|
| `invalid-config` | no | Conflicting attributes — e.g. `mode="manual"` together with `game` or `game-src` | See [widget modes](../widget.md#modes) for which combinations are valid |
| `resolve-failed` | no | Marketplace game id could not be resolved at `/games/:id/resolve` | Confirm the id is correct and currently indexed in the [marketplace](../marketplace.md) |
| `iframe-load-failed` | no | Sandboxed iframe failed to load the game script | Verify the game id is correct or your `game-src` URL is reachable. For marketplace games the SRI hash must still match — see [game-distribution](../game-distribution.md) |
| `iframe-script-blocked` | no | CSP inside the iframe blocked the game script | Marketplace games include the canonical CSP; check that your customer-hosted game URL passes the iframe's `script-src` policy |
| `game-not-registered` | no | The loaded game script did not call `register()` at top level | The author must call `register()` synchronously at module load. Confirm with the game author |
| `game-error-relayed` | no | The game's runtime threw an error and the widget forwarded it | The relayed error carries an `originalCode` in `event.detail` — share it with the game author |
| `postmessage-bad-origin` | yes | An unexpected postMessage arrived from outside the iframe | Almost always benign cross-frame noise; the widget keeps running |
| `cap-solve-failed` | yes | Cap's proof-of-work `solve()` failed (network or tab suspended mid-solve) | The widget can re-issue. Encourage the user to retry; persistent failures point at a CSP or network issue with `api.caputchin.com` |
| `cap-redeem-failed` | yes | Cap's redeem step failed | Same as `cap-solve-failed` — retry |
| `form-not-found` | no | Widget could not locate its enclosing `<form>` to inject the `caputchin-token` field | Wrap the widget inside the `<form>` you want it to protect. The widget walks up the DOM looking for an ancestor `<form>` |

## Common integration mistakes

### "Token is always `null` on the server"

The widget injects `caputchin-token` only **after** successful completion. If your form auto-submits before completion, the field is not there. Either use `mode="form-submit"` (widget gates submission until done) or wait for the `complete` event.

### "I see two tokens injected"

You mounted two `<caputchin-widget>` elements in the same `<form>`. Each one injects a `caputchin-token` field on completion, and the second overwrites the first. Mount only one widget per form.

### "Score is `null` even though the user played the game"

`score` is reported by the game, not the platform. The invisible default has no game, so score is `null`. Some games choose not to emit a score — that is also `null`. Either way, do not gate verification on `score`; it is client-claimed metadata. See [principles](../privacy.md).

### "Verification works locally but fails in production"

The widget script must be reachable from the deployed page. Cloudflare and other CDN-fronted sites should allow `cdn.jsdelivr.net`. Your CSP must allow `api.caputchin.com` for the runtime calls.

### "Hosted-verification webhook never fires"

Hosted verification is paid-tier only. Confirm the site is on the paid plan and the webhook is enabled in the [dashboard](../dashboard.md). The platform retries failed deliveries with exponential backoff; check the dashboard delivery log.

## Reporting a bug

If a verdict is wrong (verified humans rejected, or invalid tokens accepted), include:

- The verdict response body (success + error-codes)
- The wrapped token that was submitted (it is safe to share — it is single-use and already consumed)
- A timestamp accurate to the minute
- Whether the issue reproduces or is intermittent
