API Integration documentation

The captcha

The provider chain, the fallback, and ALTCHA's two endpoints.

Each website has an ordered chain of captcha providers. The script tries the first one; if it cannot load in the browser — ad blocker, corporate policy, network — it moves on to the next ALTCHA link of the chain, the only one it may choose on its own. The token obtained is sent with the submission:

X-Captcha-Provider: recaptcha_v3:12
X-Captcha-Token: 03AGdBq…

X-Captcha-Provider is the chain link key, in the form type:identifier. The server verifies the token against the designated link, and that link only.

Type Token sent
altcha Base64 payload produced by the ALTCHA widget
recaptcha_v3 Token from grecaptcha.execute(siteKey, {action: 'submit'})
recaptcha_enterprise Token from grecaptcha.enterprise.execute(siteKey, {action: 'submit'})
turnstile Token rendered by the Turnstile widget
hcaptcha Token rendered by the invisible hCaptcha widget

The default captcha

A website with an empty chain is protected by ALTCHA, self-hosted by the platform. Its chain link key is altcha, with no identifier:

X-Captcha-Provider: altcha
X-Captcha-Token: eyJhbGdvcml0aG0iOi…

No website therefore accepts a submission without a captcha, unless the platform has explicitly disabled this default.

The chain is ordered on purpose: a caller does not get to pick the least demanding link, nor to aim straight at the last one to trigger the pass-through described below. Only three links are accepted:

  • the first link of the chain;
  • the link designated by a fallback token, which the server signs once it has itself observed the failure of the previous link;
  • any ALTCHA link, verified locally by the platform — so that a visitor whose browser blocks a third party's script can still get through.

Any other link — a Turnstile, hCaptcha or reCAPTCHA link that is in the chain but was neither reached in order nor designated — is refused with 403 {"error": "captcha_provider_not_allowed"}, as is a key that is not in the chain at all. A non-designated ALTCHA link is verified, but it can neither open a fallback nor benefit from the pass-through: on it, an unreachable provider is a captcha_failed.

A token longer than 4 096 characters is refused with captcha_failed, without any call to the provider.

The fallback

The fallback is triggered by a technical failure observed by the server, never by a rejected token, and never by a failure the browser merely declares:

Situation Server response
No token although the chain expects one 403 {"error": "captcha_required"}
Token rejected by the provider 403 {"error": "captcha_failed"}
Link not accepted for this submission 403 {"error": "captcha_provider_not_allowed"}
Provider unreachable, a next link exists 403 {"error": "captcha_fallback", "next": "altcha:7", "fallback_token": "1789459320.4f3b…"}
Provider unreachable, last link the submission goes through, the incident is logged

A 4xx from the provider is a refusal, including a 429 — an exhausted quota is not an outage. Only a 5xx, or no answer at all within three seconds, counts as a failure and opens a fallback.

The fallback token

fallback_token is the server's signed authorization to use the next link. It is tied to the website and to that one link, and it is valid for two minutes.

Send it back in the body of the replayed submission, in the captcha_fallback field — not in a header, which the browser's CORS preflight would reject:

{
    "name": "Smith",
    "email": "[email protected]",
    "captcha_fallback": "1789459320.4f3b…"
}

captcha_fallback is a technical field: it is never recorded with the request.

Without it, the next link is only accepted if it is an ALTCHA link — and even then it opens no further fallback. So: on captcha_fallback, resume solving at the next link, carry the fallback_token with the replay, and do so once per link, no more.

When the server observes the failure of the last link of the chain, and the caller was entitled to that link — it was the first, or it was designated by a fallback token — the submission is accepted without verification and the incident is logged. A provider's outage must not cost every client their leads. A link reached any other way never gets this treatment.

ALTCHA

ALTCHA is a proof of work solved in the browser: nothing is sent to a third party. It has two dedicated endpoints.

GET /api/captcha/altcha.js

The ALTCHA widget, served by the platform.

Check Value
Authentication none
Cache public, max-age=31536000, immutable — the URL changes with every release

The script loads it with import() when it needs it. Responds 404 if the platform has not built its assets.

GET /api/websites/{website}/captcha/altcha/challenge

The challenge to solve, tied to the website requesting it.

Check Value
Authentication none; the Origin is authoritative
Origin checked
Rate limiting api-captcha-challenge, 30 per minute per IP
Cache Cache-Control: no-store

Response 200

{
    "algorithm": "SHA-256",
    "challenge": "9f2c…",
    "salt": "b71d…",
    "signature": "40ae…",
    "maxnumber": 50000
}

Errors

Code Body Cause
404 {"error": "altcha_not_enabled"} The website does not have ALTCHA in its chain
403 {"error": "origin_not_allowed"} The Origin does not match

ALTCHA relies on the Web Crypto API, which only exists in a secure context. A page served over plain HTTP cannot solve the challenge: the chain then moves on to the next link. Over HTTPS, the issue does not arise.

A challenge cannot be replayed

An ALTCHA response that has already been accepted is rejected the second time it is presented. Request a new challenge for each submission.