turnstile · verification loop

Turnstile verification loop: it completes, then starts over

A verification loop is the challenge that keeps coming back. The widget completes, the page reloads or the form submits, and Turnstile issues another challenge — sometimes forever. Cloudflare's own name for it is a challenge loop, and their documentation says it happens "in very specific cases where we detect strong bot signals". That is one of five documented causes, and the others are far more common in practice.

This is not the spinner that never finishes

Two symptoms get reported with the same words and have almost nothing in common:

SymptomWhat it isWhere to go
The widget never resolves — spinner, no tokenThe challenge did not completestuck on verifying you are human
The widget completes, then challenges againThe challenge completed and was re-issuedThis page
The widget mounts and then stops respondingA stalled widget, reported as 300030error 300030

The distinction matters because the fixes do not overlap. A challenge that never completes is a client-side problem — a blocked script, an unsupported browser, a container with no height. A challenge that completes and repeats is about what happened to the token afterwards, or about signals that made Cloudflare ask again.

Cloudflare's five documented causes

From the challenge solve issues page, verbatim, a challenge loop can happen because of:

  • Network issues. Poor or unstable connections prevent the challenge from being completed.
  • Browser configuration. Settings or extensions may block the scripts needed to execute the challenge.
  • Unsupported browsers. A browser Turnstile does not support.
  • JavaScript disabled. Turnstile relies on JavaScript to function.
  • Detection errors. If Turnstile suspects bot-like behaviour, repeated challenges are issued.

Cloudflare adds that most challenges complete in a few seconds, and that a longer one is a sign to check the network. Their troubleshooting sequence is browser compatibility, then extensions, then JavaScript, then private mode, then another device, then no VPN, then another network.

That list is written for a visitor stuck on someone else's site. If you own the site, the useful version is different — most loops reported by developers are not detection at all.

The causes that are yours to fix

The token was never validated. A token proves nothing until your server hands it to siteverify. If your handler skips validation on some path — an early return, a cached response, a route that bypasses middleware — the gate in front of it re-challenges because nothing ever cleared it.

The token was validated twice. A Turnstile token is single-use and valid for 300 seconds. The second validation of the same token returns success: false with timeout-or-duplicate, and code that treats any failure as "challenge again" produces a loop by itself. Retry middleware, double-submitted forms and a validation call in both a middleware and a controller all cause this. See timeout-or-duplicate.

The widget was reset without being re-rendered. After a failed submit, calling turnstile.reset() returns the widget to its unsolved state. If your code resets on every submit — including the successful ones — the visitor solves, submits, and is handed a fresh challenge as a reward.

Pre-clearance is expected but not configured. In pre-clearance mode Turnstile sets a cf_clearance cookie so a solved challenge satisfies the WAF for later requests. That cookie is set by a fetch to a /cdn-cgi/ endpoint on your own domain, and Cloudflare documents that the request needs 'self' in your connect-src. A CSP that omits it means the challenge succeeds, no clearance cookie is written, and the next protected request challenges again — which is exactly what a loop looks like. See pre-clearance and cf_clearance.

The cookie cannot survive. A clearance cookie set on www.example.com does nothing for example.com. Cookie blocking, private windows, SameSite interactions on a cross-site POST and a mismatched host all produce a loop that looks like detection and is not.

Narrowing it down

Work from the token outwards. Each step rules out a whole class:

bash
# 1. Did a token arrive at all? Read the hidden field after solving.
#    Empty -> not a loop; the challenge is not completing. Go to
#    /turnstile-errors/stuck-verifying-you-are-human instead.

# 2. Did your server validate it, and what did Cloudflare answer?
curl -s https://challenges.cloudflare.com/turnstile/v0/siteverify \
  -d secret="$TURNSTILE_SECRET" \
  -d response="$TOKEN" | jq

#    {"success": true}                        -> the token was fine; the loop is
#                                                downstream (cookie, redirect, reset)
#    "timeout-or-duplicate"                    -> validated twice, or older than 300s
#    "invalid-input-response"                  -> wrong secret for this sitekey

# 3. Is a clearance cookie being set? Pre-clearance only.
curl -sI https://example.com/protected | grep -i set-cookie | grep cf_clearance

If step 2 says success: true and the loop continues, nothing is wrong with Turnstile: the token was good and something after it did not record that fact. That is a session, cookie or redirect bug, and it is where to spend the next hour.

If you reach the end of that and the widget is genuinely being re-challenged on a clean browser with a valid token, you are in the detection case Cloudflare describes — and there, the answer is a feedback report through the widget's Submit Feedback control rather than a code change.

Loops in automation, which are a different problem

A loop in a test suite or a scripted client usually is detection, because the client genuinely is automated. Cloudflare's own testing guidance says automated suites driven by Selenium, Cypress or Playwright are detected as bots, and recommends the documented dummy sitekeys precisely so that tests stop depending on passing a real challenge. For anything that is really testing your own form logic, the test keys are the right answer and they are deterministic.

When the challenge itself is what has to be cleared — a staging environment behind a real widget, synthetic monitoring against production, a property you are authorised to test — a solver returns a real token for the sitekey and page URL, and the mode makes no difference to the request. Prepaid credits start at $0.40 per 1,000 solves and fall to $0.075 at volume; failed solves are never billed; the first 1,000 are free. The solve reference has the call.

Common questions

Most often because nothing recorded that you solved it. A token proves nothing until your server validates it with siteverify, and in pre-clearance mode a clearance cookie has to be written and then survive. If either step is missing the next request looks unverified, which is indistinguishable from a loop.

No. A loop means the challenge completed and was issued again; a spinner means it never completed. They have separate causes and separate fixes — a loop is usually about the token or the cookie afterwards, a spinner about the script or the container.

Yes, and it is a common one. Tokens are single-use with a 300-second lifetime, so the second validation returns success false with timeout-or-duplicate. Code that re-challenges on any validation failure then loops indefinitely, and both calls looked correct in isolation.

Sometimes, but it is the last thing to conclude rather than the first. Cloudflare lists detection as one of five causes alongside network problems, browser extensions, unsupported browsers and disabled JavaScript — and on a site you control, an unvalidated token or a clearance cookie that cannot be set explains far more loops than detection does.

Related

Sources

More in Turnstile errors

Turnstile error 300030Error 300030 means a Turnstile widget mounted and then stopped responding. The real causes, the codes it gets confused with, and the fixes.Turnstile timeout-or-duplicate errorsiteverify returns timeout-or-duplicate when a Turnstile token is verified twice or is older than 300 seconds. The causes, and how to fix each one.Turnstile callback not firingWhy the Turnstile success callback never runs: implicit rendering, callbacks not on window, hidden containers and SPA remounts.Cloudflare Turnstile error 600010Turnstile error 600010 is a generic challenge failure in the 600* family. What Cloudflare documents, the reproduced causes, and how to fix it.cf-turnstile-response invalidThree different failures wear this name. Sort out which one you have — the token never arrived, siteverify refused it, or your keys do not match.Turnstile stuck on "Verifying you are human"Why Turnstile hangs on "Verifying you are human": how to read the error code, plus fixes for CSP, clock skew, blocked domains and headless CI.invalid-input-responsesiteverify returns invalid-input-response when a token is invalid, malformed or expired. The causes worth checking, in the order they occur.invalid-input-secretsiteverify returns invalid-input-secret when your secret key is rejected. Why the key is usually right, and the five things that actually cause it.Token rejected"Token rejected" is not a Cloudflare error — it is what an app prints when siteverify refused its token. What to do, as a visitor or as a developer.Field is requiredA Laravel validation message meaning the Turnstile token never reached your server. Why the field is empty, how to tell which cause it is, and the fix for each.Turnstile 401 in consoleA 401 on a /cdn-cgi/challenge-platform/ pat/ request is expected. Cloudflare documents it: the browser could not get a Private Access Token, and falls back.Turnstile script fails to loadapi.js never runs, or turnstile is undefined when you call render. The four causes worth checking first, starting with the one nobody expects: Rocket Loader.

Automating a gate you own or are authorised to test?

// SolveGate clears Cloudflare Turnstile and WAF challenges through one REST call · first 1,000 solves free