Turnstile token missing: how to fix it

The error means one thing: when the form was submitted, the hidden field Turnstile fills with the token was still empty. Here are the causes, most common first.

What the error is telling you

After a visitor passes Cloudflare Turnstile, the widget writes a token into a hidden field named cf-turnstile-response. If the server receives the request with that field empty, it reports a missing token. So the problem is almost always that the token was not ready at the moment of submit.

Cause 1: the form submits before the callback fires

Turnstile is asynchronous: it checks browser behaviour first, then generates the token and calls the callback you configured. If the submit button posts the form the instant it is clicked, that can happen before the callback. Submit from inside the callback, or set a flag there and check it before submitting.

// Implicit rendering: submit from the callback Turnstile calls for you
function onTurnstileSuccess(token) {
  document.querySelector('#my-form').requestSubmit()
}

// Or explicit rendering: only let the submit through once a token exists
const widgetId = turnstile.render('#turnstile-box', {
  sitekey: SITEKEY,
  callback: () => { ready = true },
})

form.addEventListener('submit', (e) => {
  if (!turnstile.getResponse(widgetId)) e.preventDefault()
})

A token is single use: once submitted it is spent, so do not retry with the same one.

Cause 2: the widget never rendered

If the Turnstile box never appears, the token stays empty. Usually the script did not load, the container did not exist, or implicit and explicit rendering were mixed. With explicit rendering, the element carrying class="cf-turnstile" needs data-render="explicit", otherwise the widget is not created.

Cause 3: sitekey or domain mismatch

A sitekey is bound to domains. If the page is deployed on a domain that was not added to the Turnstile configuration (for example you moved from example.com to www.example.com, or you are on a preview domain), the widget keeps failing and there is no token. In the Cloudflare dashboard, confirm the domain list for this sitekey includes the domain you are actually on.

For local development, either add localhost to the allowed domains, or use the official Cloudflare test sitekey, which always passes and is meant for integration work.

The token arrives but the server still rejects it

If your case is automation

The checks above assume a page that is used by a person. If the page is driven by a script with no human interaction, Turnstile usually will not hand out a token. The fix is not in the front end: get a token server side from a recognition service and inject it. It works the same way as hCaptcha: submit the sitekey and the page URL to the recognition endpoint, then put the token into cf-turnstile-response.

FAQ

Why does it work locally but report a missing token in production?

Most often the production domain was not added to the Turnstile allowed list, so the widget never passes and there is no token. The next most common cause is an extra cache or CDN layer in production that makes the submit happen before the callback.

Do I have to add the cf-turnstile-response field myself?

No. Turnstile inserts that hidden field into its own form once verification passes. Just read it at the moment of submit, not when the page loads.

Can a token be submitted more than once?

No. A token is single use and lives for about 300 seconds; submitting the same one twice fails.

Is a token from a recognition API the same as one a person passes?

To the back end they are the same thing: a submittable cf-turnstile-response. Only the way you obtained it differs.

Related

Read the API reference Download the Chrome extension