Troubleshooting

Fix Token Expired Before Submission

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:

  1. Time between solve and submit: Should be <60 seconds
  2. Correct target URL: The pageurl in solve must match the submission URL
  3. Correct sitekey: Mismatched sitekey produces invalid tokens
  4. 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)

No comments yet.