Skip to content

"reCAPTCHA verification failed" is the same error message regardless of why verification failed. The token expired, the keys don't match, the widget didn't load, the user is on a VPN, the score is below your threshold, the form was submitted twice — all of these surface as the same generic failure. Debugging means working through the list in order of likelihood.

The user-side causes

These are the ones you can't fix from your side, but you can stop blaming your code for them.

Browser extensions block the script. uBlock Origin, Privacy Badger, and most tracking blockers either block google.com/recaptcha/api.js outright or break it by removing inline event handlers. About 20–30% of technical users have at least one extension that interferes. The widget never loads, your form never gets a token, the submit fails. Detection: open the browser console, look for Refused to load the script or net::ERR_BLOCKED_BY_CLIENT. Fix: nothing on your side; surface a clearer error than "verification failed."

The token expired. reCAPTCHA tokens are valid for two minutes. If your form takes longer than that to submit (multi-step flow, file upload, user got distracted), the token is stale by the time you verify it. Fix: regenerate the token at submit time, not at form load.

VPN or datacenter IP. v3 will return a low score for traffic from cloud or VPN ranges. v2 will throw extra puzzles. Both can fail entirely on heavily abused ranges. Fix: again, not yours to fix, but worth telling the user "try without your VPN" rather than "verification failed."

Stale cookies. reCAPTCHA depends on a Google cookie state. Users who clear cookies mid-session, or use Safari's "Prevent Cross-Site Tracking," will see verification fail intermittently. Fix: reload the widget on the client when verification fails, instead of forcing a full form submit.

The configuration causes

These are yours.

Sitekey doesn't match the domain. The number-one cause of "captcha token validation failed" is a sitekey registered for example.com being served from www.example.com, staging.example.com, or a Vercel preview domain. The widget will load and produce a token; verification will fail server-side because the domain doesn't match the registration. Fix: register all subdomains, or use a wildcard registration if your tier supports it.

Test keys in production. Cloudflare's 1x... test keys, Google's test keys, and CaptchaLa's demo_app are all designed to bypass real scoring. If they reach production, your verification is theatre. Fix: a CI lint rule that fails on hardcoded test key strings outside test directories.

No server-side verification. A token that's only checked client-side is trivial to bypass. Send any string, let the form submit, profit. This sounds obvious, but a depressing number of forms still don't call siteverify server-side at all. Fix: every token must go to the server, and the server must verify before processing.

http
POST https://www.google.com/recaptcha/api/siteverify
Content-Type: application/x-www-form-urlencoded

secret=YOUR_SECRET_KEY&response=TOKEN_FROM_CLIENT&remoteip=USER_IP

The response includes success, score (v3 only), action, and hostname. All four matter. Most integrations check success and skip the rest, which means a token issued for a different action on a different hostname will still pass.

The score-threshold causes (v3-only)

v3 returns a score; you set the threshold. If your threshold is 0.7 and most legitimate users score around 0.6 because they have privacy extensions, almost everyone fails verification. The fix is not to raise the threshold but to instrument and observe.

Log the score for every verified request. After a week, plot the distribution. The bimodal split between bots and humans usually shows up around 0.3. Set the threshold there. Recheck monthly because traffic changes.

A verification flow that works

StepCheckFailure mode
1Widget loadedAdblocker / network
2Token generatedJavaScript error
3Token sent to serverForm bug
4siteverify returns successWrong key / expired token
5Hostname matchesWrong domain registered
6Action matches (v3)Wrong action label
7Score above threshold (v3)Threshold too high

Each step needs its own logged error, not a generic "verification failed." If you can't tell which step a given failure happened at, you can't fix it.

When the platform is the problem

Sometimes verification fails because the vendor has flagged your traffic. v3 in particular gets noisy when your real users overlap with traffic patterns it considers suspicious — single-page apps with heavy JS routing, Tor users, certain corporate networks. Switching vendors is a real option, not a last resort. CaptchaLa and other modern providers expose verification reasons in the response payload (low IP reputation, replayed token, action mismatch, fingerprint anomaly), which removes most of the guessing this post is about.

Recap

Verification failures are usually one of: extension blocking the script, token expired, wrong domain, missing server-side check, or threshold too high. Work through them in order. Stop showing users a single "verification failed" string when you have seven distinct error states behind it.

Articles are CC BY 4.0 — feel free to quote with attribution