You automated the boring part. The solver returned a token, you dropped it into the form, you hit submit, and the site came back with "verification failed". No stack trace, no useful error, just a page that looks exactly like the one you started on.
This happens to everyone who automates anything behind a captcha, whether it's an end-to-end test suite for your own signup flow, a monitoring job, or an agent that fills in forms. The token itself is rarely "wrong". Almost always, it's valid but used in a way the site's backend refuses.
Here are the seven causes I see most often, roughly in order of how often they bite, and what to do about each one.
1. The token expired before you submitted it
Captcha tokens are short-lived on purpose.
- reCAPTCHA: Google's docs say each response token "is valid for two minutes, and can only be verified once."
- Cloudflare Turnstile: tokens are valid for 300 seconds (five minutes) after they're generated.
Two minutes sounds like plenty until you look at what a real script does between "token received" and "form submitted": it waits for a page to settle, fills ten other fields, uploads a file, retries a flaky selector. If your flow takes 90 seconds before it even asks for the token, you're cutting it close.
Fix: request the token as late as possible, right before submit. Don't fetch it at the start of the test "to have it ready". If your form has a long multi-step flow, fill everything else first and solve the captcha last.
2. You used the same token twice
Both Google and Cloudflare reject a token the second time it's verified. On the server side this shows up as timeout-or-duplicate, and both vendors use that same code for "too old" and "already used", which is why it's so confusing.
The classic way to hit this: a retry loop. The submit fails for an unrelated reason (a validation error on another field, a 502 from the backend), your code retries the submit, and the retry carries the token that the first attempt already burned.
Fix: treat a token as single-use in your code too. If you retry a submit, get a fresh token for the retry.
3. The sitekey or page URL doesn't match the page you're on
A token is tied to the sitekey it was issued for and to the site it was solved on. Google returns a hostname field in the verification response so the site can check where the challenge was solved.
Two ways this goes wrong:
-
Wrong sitekey. Many sites have more than one: a staging key and a production key, or a separate key for the login page and the checkout page. Scrape the
data-sitekeyfrom the exact page you're submitting, not from a config file you copied last month. - Wrong URL. Send the full URL of the page where the captcha actually appears, not the site's homepage. For an SPA, that's the route the widget renders on.
4. reCAPTCHA v3: wrong action, or a score below the site's threshold
v3 doesn't show a challenge. It returns a score between 0.0 and 1.0, and Google's own guidance is that "by default, you can use a threshold of 0.5". Each site picks its own threshold, and it can be stricter on sensitive pages.
v3 also has actions. Google tells site owners to "verify that the action name is the name you expect". If the page calls grecaptcha.execute(key, { action: 'login' }) and your token was generated for submit or no action at all, a careful backend will reject it even with a decent score.
Fix: read the action name from the page's own JavaScript (search the bundle for execute() and pass it through when you request the token. Only ask for a minimum score as high as the site actually needs. If you want the longer version, including why free keys only ever return 0.1, 0.3, 0.7 or 0.9, I wrote up how reCAPTCHA v3 scores work.
5. The token was earned by a different browser than the one submitting it
This is the one that catches people with Turnstile and with anti-bot vendors like DataDome. The token or cookie is minted in one browser context (one IP address, one user agent) and then submitted from another.
- If you solve through a remote service, pass your own proxy so the solve happens from the same IP your script uses.
- Pass the same user agent your browser uses, or, if the solver hands back the user agent it used, set your browser to that one before submitting.
Fix: keep the IP and user agent consistent between "earn the token" and "use the token". For DataDome you'll get a cookie rather than a token; set it on the same session and keep using the same proxy. (More on what Turnstile actually checks in How Cloudflare Turnstile works.)
6. You put the token in the wrong place
Each widget expects the token in a specific spot:
-
reCAPTCHA v2 and v3: a hidden
textareanamedg-recaptcha-response. -
Turnstile: a hidden input named
cf-turnstile-response.
Many modern sites don't read that field at all. They register a JavaScript callback (data-callback="onSolved") and only enable the submit button, or only attach the token to their fetch request, when that callback fires. Setting the field's value does nothing if the callback never runs.
Fix: check the widget's data-callback attribute. If there is one, call that function with the token after you set the field (this is Puppeteer/Playwright-style; there's a full Playwright example if you need the surrounding code):
await page.evaluate((token) => {
document.querySelector('[name="g-recaptcha-response"]').value = token;
const cb = document.querySelector('[data-sitekey]')?.getAttribute('data-callback');
if (cb && typeof window[cb] === 'function') window[cb](token);
}, token);
7. It's reCAPTCHA Enterprise, not classic reCAPTCHA
Enterprise keys look the same but load from recaptcha/enterprise.js and are verified through a different API. A token solved as classic v2 or v3 won't pass an Enterprise check.
Fix: look at the script tag. If you see enterprise.js, or calls to grecaptcha.enterprise.execute, tell your solver it's Enterprise.
A quick checklist before you blame the solver
- Am I requesting the token right before submit?
- Does every retry get a fresh token?
- Is the sitekey from this exact page, and am I sending this exact URL?
- For v3: the right action, and a score the site will accept?
- Same IP and user agent for solving and submitting?
- Is the token in the right field, and did the callback fire?
- Is it actually Enterprise?
How we handle this at CaptchaNo
Full disclosure: I work on CaptchaNo, a captcha solving API, and several of these causes shaped how the API works. One request covers reCAPTCHA v2, v3 and Enterprise, Turnstile, DataDome and image captchas. The call waits for the token instead of making you poll, which helps with cause #1. You can pass proxy, userAgent, action and minScore for causes #4 and #5, and failed solves aren't billed. The API reference has the full request format, and there's an open-source MCP server if your automation is an AI agent. Even if you use something else, the checklist above applies to every solver. If you're comparing services, I also wrote a guide to choosing a captcha solving API.
Whatever you're automating, keep it to things you're allowed to automate: your own sites, systems you have permission to test, and data collection the site's terms permit.
Top comments (0)