> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: TURNSTILE_REQUIRED (422) on public form endpoints

> Resolve the 422 TURNSTILE_REQUIRED rejection on the public marketing-leads ingest and the public DSA-request portal — Cloudflare Turnstile token missing, replayed past its single-use window, or failed siteverify; the cause table, the widget wiring fix, the retry posture, and the escalation bundle.

# Troubleshooting: TURNSTILE\_REQUIRED (422) on public form endpoints

Two public, unauthenticated endpoints carry the Cloudflare Turnstile
bot-protection gate — the marketing-lead capture used by Devotel-property
contact forms, and the self-service data-subject-request portal
(`POST /compliance/public/dsar/begin`). When the gate is armed and the
submitted token fails, the request is rejected at the bot-protection
step — before rate limits, before payload validation completes — with:

```json theme={null}
{
  "error": {
    "code": "TURNSTILE_REQUIRED",
    "message": "Bot-protection token missing or invalid. Please refresh the page and try again.",
    "status": 422,
    "details": {
      "error_codes": ["invalid-input-response"]
    }
  }
}
```

The 422 is **fail-closed by design**: a request without a challenge the
platform can verify reads as a bot submission, and the gate rejects it
rather than let it in unchallenged. An absent, expired, or forged token
never succeeds while the gate is armed.

<Note>
  `error.details.error_codes` carries the raw codes Cloudflare returned
  from siteverify when it available — the most common entries are
  `missing-input-response` (no token arrived at all), `invalid-input-response`
  (token already used, expired, malformed, or from the wrong site), and
  `timeout-or-duplicate` (the token was presented before, or too late).
  The field is omitted when the verification itself was unreachable, in
  which case the rejection is an availability fault on the platform side,
  not a visitor error.
</Note>

## Cause table

| Cause | How to recognize it | Fix |
| - | - | - |
| **No widget on the form** — the page submits without rendering a Turnstile challenge, so `turnstile_token` is absent. | `error_codes: ["missing-input-response"]`; rejects on every submission, for every visitor. | Render the Turnstile widget on the form and submit its callback token as `cf-turnstile-response` (for the marketing-leads body, map it to `turnstile_token`). |
| **Token replayed past its single-use window** — the same token is attached to more than one submission, or attached minutes after it was solved. | First submission succeeds, immediate duplicates fail with `invalid-input-response` or `timeout-or-duplicate`. | Request a fresh token per submission — re-render or reset the widget after every submit, success or failure. Never cache or reuse the token. |
| **Site-key mismatch** — the widget was rendered with a site key from a different environment than the one the API verifies with (staging widget against production, or a key from another account). | `invalid-input-response` for every visitor even on a fresh token. | Pair the page's public site key with the environment you submit to; a staging page cannot produce tokens that production accepts. |
| **Visitor-network bot flag** — the token verified but the challenge itself marked the solver as automated (headless browser, aggressive VPN exit, synthetic traffic). | Sporadic rejects concentrated on one network or one automation runner; humans on ordinary connections pass. Whole-request rate with ordinary browser traffic stays clean. | Have the visitor retry on an ordinary browser and connection; if the pattern persists across a real visitor population, capture the escalation bundle below — this is the one cause class that is genuinely Cloudflare-side. |

## Fix it at the form

The gate is armed server-side; the fix is always on the form side:

1. **Render the widget.** Add the Cloudflare Turnstile script and render
   the widget with the public site key in your form markup. The widget
   solves the challenge and hands you a token via its callback.
2. **Attach the token to the submission.** For the marketing-lead
   capture endpoint this is the `turnstile_token` body field; on a raw
   HTML form it is the `cf-turnstile-response` field. Whichever name,
   it is the same token.
3. **Reset after every attempt.** Reset the widget after each
   submission — success included — so every attempt carries a fresh
   token. A reused token is the single most common cause of this 422.

## Retry posture

**Retry only after solving a fresh token — never blind-retry.** A 422
`TURNSTILE_REQUIRED` is a deterministic gate: replaying the identical
body rejects identically, and the rate limit behind the gate makes a
tight retry loop expensive. Re-render or reset the widget, get a new
token, and submit once. If a fresh token still rejects with a site-key
mismatch or with no `error_codes` in the response, work the cause table
above before trying again.

## What NOT to do

* **Do not retry with the same token.** Single-use means single-use;
  a replayed token is the most rejected class and always fails.
* **Do not cache tokens** client-side or server-side "to save solves."
  A cached token is already dead by the time you use it.
* **Do not strip the field to see if the gate is off.** The gate is
  fail-closed — an absent token rejects identically to a bad one. The
  absence test tells you nothing.
* **Do not open the form wider to route around the gate.** A public
  endpoint with no abuse defense fills your lead pipeline with
  synthetic submissions; the gate exists because it is the cheaper
  failure of the two.

## Escalation bundle

Work the cause table first — the four causes above account for the
rejection almost always. If a fresh, in-window token with the correct
site-key pairing still rejects, open a ticket with:

* The **request id** from the response envelope's `meta.request_id`.
* The **public site key** the widget was rendered with (the public key,
  never the secret).
* The **full error envelope**, including `error.details.error_codes`.
* The endpoint you hit (marketing-lead capture, or the DSA-request
  portal's `begin`) and one confirmation that a fresh token was used.

## See also

* [Error codes reference](/reference/error-codes) — the full envelope
  and where `TURNSTILE_REQUIRED` sits in the catalog.
* [Submit a marketing lead](/api-reference/endpoints/public) — the
  endpoint contract, including the `turnstile_token` field.
* [DSA requests — public self-service portal](/compliance/dsar) — the
  portal flow that carries the same gate.
* [Rate limits and cool-downs](/troubleshooting/rate-limits) — the 429
  envelope you will hit if you blind-retry through the gate.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.