You solved the CAPTCHA, but the target site rejects the token. The most common cause: the token expired before you submitted it.
Token Lifetimes
| CAPTCHA Type | Token Lifetime | Notes |
|---|---|---|
| reCAPTCHA v2 | ~120 seconds | From solve time, not submit time |
| reCAPTCHA v3 | ~120 seconds | Shorter effective window |
| reCAPTCHA Enterprise | ~120 seconds | May vary by configuration |
| Cloudflare Turnstile | ~300 seconds | More forgiving |
| Cloudflare Challenge | ~30 minutes | cf_clearance cookie lifetime |
| hCaptcha | ~120 seconds | Similar to reCAPTCHA |
| Image/OCR | N/A | Text doesn't expire |
Why Tokens Expire
1. Slow Processing After Solve
# ❌ BAD: Processing data between solve and submit
token = solver.solve(params)
# This takes 30 seconds...
processed_data = heavy_computation()
more_data = fetch_from_database()
# Token may be expired by now
requests.post(url, data={
"g-recaptcha-response": token, # EXPIRED
"data": processed_data,
})
2. Solving Too Early
# ❌ BAD: Pre-solving tokens before they're needed
tokens = [solver.solve(params) for _ in range(10)]
# By the time you use token #10, token #1 has expired
3. Queue/Batch Delays
# ❌ BAD: Tokens sitting in a queue
queue = []
for url in urls:
token = solver.solve(params)
queue.append((url, token))
# Later processing — tokens at the start of queue are expired
for url, token in queue:
submit(url, token)
Solutions
Solution 1: Submit Immediately
Use the token within seconds of receiving it:
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_and_submit(form_url, site_key, form_data):
"""Solve CAPTCHA and submit form immediately."""
# Solve
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": form_url,
})
task_id = resp.text.split("|")[1]
# Poll
while True:
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id,
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
token = result.text.split("|", 1)[1]
break
# Submit IMMEDIATELY — no delay
form_data["g-recaptcha-response"] = token
return requests.post(form_url, data=form_data)
Solution 2: Solve Just-in-Time
Don't pre-solve. Solve when you actually need the token:
async def process_url(url, site_key, solver, session):
"""Process one URL with just-in-time solving."""
# Do all prep work BEFORE solving
page = await session.get(url)
form_data = extract_form_fields(page.text)
# Solve right before submission
token = await solver.solve(session, {
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": url,
})
# Submit immediately
form_data["g-recaptcha-response"] = token
return await session.post(url, data=form_data)
Solution 3: Solve and Submit in Pipeline
For batch processing, pair solving with immediate submission:
async def batch_process(urls, site_key):
solver = AsyncCaptchaAI(os.environ["CAPTCHAAI_API_KEY"])
async with aiohttp.ClientSession() as session:
tasks = [
solve_and_submit_async(url, site_key, solver, session)
for url in urls
]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def solve_and_submit_async(url, site_key, solver, session):
token = await solver.solve(session, {
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": url,
})
# Submit within milliseconds of solving
async with session.post(url, data={
"g-recaptcha-response": token,
}) as resp:
return await resp.text()
Solution 4: Add Token Freshness Check
Track when tokens were solved and skip expired ones:
import time
class TokenManager:
def __init__(self, ttl=90):
"""TTL in seconds — use 90 for a 120s token lifetime."""
self.ttl = ttl
self.tokens = {}
def store(self, key, token):
self.tokens[key] = (token, time.time())
def get(self, key):
if key not in self.tokens:
return None
token, created = self.tokens[key]
if time.time() - created > self.ttl:
del self.tokens[key]
return None # Expired
return token
def is_fresh(self, key):
return self.get(key) is not None
Debugging Token Rejection
When a token is rejected, check:
- Time between solve and submit: Should be <60 seconds
- Correct target URL: The
pageurlin solve must match the submission URL - Correct sitekey: Mismatched sitekey produces invalid tokens
- Session consistency: Use the same cookies/session for the entire flow
import time
start = time.time()
token = solver.solve(params)
solve_time = time.time() - start
print(f"Solve took {solve_time:.1f}s")
# Submit
resp = requests.post(url, data={"g-recaptcha-response": token})
total_time = time.time() - start
print(f"Total time: {total_time:.1f}s")
if total_time > 90:
print("WARNING: Token may have expired")
FAQ
How do I know if a token was rejected due to expiry?
The target site typically returns a form error or redirects to the CAPTCHA page. It won't tell you "token expired" explicitly.
Can I extend a token's lifetime?
No. Token lifetime is set by Google/Cloudflare and cannot be modified.
Is reCAPTCHA v3 more sensitive to timing?
reCAPTCHA v3 has the same 120-second lifetime, but sites may apply stricter validation windows. Submit v3 tokens as quickly as possible.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.