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.

codeWhat to change
ORIGIN_UNAUTHORIZEDThe 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_INVALIDThe site key is not a current live or test key. Rotating keys in the dashboard invalidates the previous pair.
SITE_DISABLEDThe site is disabled. Re-enable it in the dashboard.
ORIGIN_REQUIREDThe browser did not send an Origin header the server could parse. file:// pages cannot create challenges.
VALIDATION_FAILEDaction 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_LIMITEDA per-minute bucket is full. The response includes Retry-After. Limits are listed on How verification works.
SERVICE_UNAVAILABLEThe 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 …/simulate returns TEST_MODE_REQUIRED.
  • A site_test_… challenge can be completed with Simulate agent, or by a real agent. The token is still mode: "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.

codeHTTPMeaning
TOKEN_TEST_MODE400The token is mode test and the body did not set allow_test to true. Expected in production.
TOKEN_ALREADY_USED409A previous call consumed this token. Consume once, on the request that grants the action.
TOKEN_SITE_MISMATCH400site_key does not match the token audience. Live and test keys are different audiences.
TOKEN_EXPIRED400The token's exp has passed. Tokens last 10 minutes from issue.
TOKEN_INVALID400The 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.
  • async or defer on the widget script means a following inline script can run before imnotahuman exists. Use the queue pattern on the JavaScript page, or load the script without async.

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.