Website integration
Troubleshooting
Why a challenge fails to appear, why the widget stays pending, and what each token error from POST /api/v1/tokens/verify means.
The widget shows a short message. The code to branch on is error in the onChange event, or error.code in an API response. Messages below are the ones the API returns.
The widget shows an error instead of a challenge
Challenge creation failed. The widget status is ERROR. These are the codes it maps to a sentence; any other code is shown as the API message.
| code | What to change |
|---|---|
ORIGIN_UNAUTHORIZED | The page origin is not allowed for this site key. Add the exact origin (scheme, host, port) or use https on the site's domain with no port. http://localhost is not implied by the domain. |
SITE_KEY_INVALID | The site key is not a current live or test key. Rotating keys in the dashboard invalidates the previous pair. |
SITE_DISABLED | The site is disabled. Re-enable it in the dashboard. |
ORIGIN_REQUIRED | The browser did not send an Origin header the server could parse. file:// pages cannot create challenges. |
VALIDATION_FAILED | action or intent contains a character outside A–Z, a–z, 0–9, and _ . : -, or is longer than 64 characters, or the JSON has an unknown field. Bodies are strict. |
RATE_LIMITED | A per-minute bucket is full. The response includes Retry-After. Limits are listed on How verification works. |
SERVICE_UNAVAILABLE | The widget uses this when the request fails in the browser, and when the response has no error code. Check /api/v1/health and the page CSP. |
The widget stays on PENDING
PENDING means a challenge exists and no proof has been accepted. The widget polls GET /api/v1/challenges/:id every 2 seconds (3 seconds after 30 seconds, 5 seconds while the tab is hidden). It does not flip to VERIFIED by itself.
- A
site_live_…challenge waits for a registered agent to sign it. There is no simulate button on live challenges.POST …/simulatereturnsTEST_MODE_REQUIRED. - A
site_test_…challenge can be completed with Simulate agent, or by a real agent. The token is stillmode: "test". - If the countdown hits zero, the status becomes EXPIRED. Default lifetime is 120 seconds. Retry issues a new challenge. The previous token is discarded.
Simulate agent is missing
The button is rendered only while status is PENDING and the challenge mode is test. A live key, a key from another site, or a challenge that already expired will not show it.
The browser says verified and the server does not
The callback and the hidden input are page state. Verify the token. A 200 from POST /api/v1/tokens/verify is the result to trust.
| code | HTTP | Meaning |
|---|---|---|
TOKEN_TEST_MODE | 400 | The token is mode test and the body did not set allow_test to true. Expected in production. |
TOKEN_ALREADY_USED | 409 | A previous call consumed this token. Consume once, on the request that grants the action. |
TOKEN_SITE_MISMATCH | 400 | site_key does not match the token audience. Live and test keys are different audiences. |
TOKEN_EXPIRED | 400 | The token's exp has passed. Tokens last 10 minutes from issue. |
TOKEN_INVALID | 400 | The string is not a token this service issued, or the signing key does not match. Check you sent result.token or the imnotahuman-token field, not the challenge id. |
WIDGET_LOAD_FAILED is not an API code. The React sample on the React page sets it on onChange when widget.js fails to load.
The form submits an empty token
- The input name is
imnotahuman-token. It stays empty until the server reports VERIFIED. - The input is inside the widget host, which is inserted immediately after the script tag. If that tag is outside the form, the token is not a form field.
- In React, the host is inside the component's container. The component has to be a descendant of the form.
asyncordeferon the widget script means a following inline script can run beforeimnotahumanexists. Use the queue pattern on the JavaScript page, or load the script withoutasync.
Content Security Policy
Allow script-src and connect-src for https://imnotahuman.ai. The samples set data-api, and the React api option, to that origin. The widget appends /api/v1 when the value does not already end in /v1. Styles are injected inside the shadow root, so a strict style-src needs 'unsafe-inline' for the widget to paint.
VALIDATION_FAILED on a request that looks right
Unknown JSON fields are rejected. /tokens/verify accepts only token, site_key, consume, and allow_test. Send Content-Type: application/json. A missing or non-JSON content type returns UNSUPPORTED_MEDIA_TYPE.