Every reCAPTCHA interaction produces a token — a long base64-encoded string that proves the challenge was completed. This token has a strict lifecycle: it is generated once, expires quickly, and can only be validated once by Google's server. Misunderstanding this lifecycle is the #1 cause of "token expired" and "invalid token" errors in automation workflows. This guide covers every stage of the token lifecycle with practical timing recommendations.
Token lifecycle overview
User completes CAPTCHA challenge (or invisible JS runs)
↓
Client receives g-recaptcha-response token (~500-4000 characters)
↓
Token placed in hidden form field or JavaScript variable
↓
Form submitted to your server with token included
↓
Server sends token to Google siteverify API
↓
Google validates: Is token valid? Expired? Already used? Correct domain?
↓
Google returns: { success: true/false, score: 0.0-1.0 (v3), error-codes: [] }
↓
Token is now consumed — cannot be reused
Stage 1: Token generation
reCAPTCHA v2 (checkbox)
Token is generated when the user completes the checkbox challenge:
// Token appears in a hidden textarea
// <textarea id="g-recaptcha-response" name="g-recaptcha-response">TOKEN_HERE</textarea>
// Or programmatically:
const token = grecaptcha.getResponse();
reCAPTCHA v2 Invisible
Token is generated when execute() is called and the risk check passes:
grecaptcha.execute();
function onCallback(token) {
// token is the g-recaptcha-response value
document.getElementById('captcha-field').value = token;
document.getElementById('form').submit();
}
reCAPTCHA v3
Token is generated for every execute() call with an action name:
grecaptcha.execute('SITE_KEY', { action: 'login' }).then(function(token) {
// token generated based on behavioral analysis
// No visible challenge — token always generated
});
Token format
A reCAPTCHA token looks like this (truncated):
03AGdBq26...long_base64_string...xYz_4kRw
- Length: 500-4000 characters
- Format: URL-safe base64
- Encoding: Contains encrypted challenge result, timestamp, site key, action (v3)
- One-time use: Each token can only be verified once
Stage 2: Token expiration
This is the most critical stage for automation. reCAPTCHA tokens expire, and the expiration window differs by version.
Expiration windows
| Version | Expiration time | Notes |
|---|---|---|
| reCAPTCHA v2 | 120 seconds (2 minutes) | Timer starts when checkbox is checked |
| reCAPTCHA v2 Invisible | 120 seconds | Timer starts when execute() callback fires |
| reCAPTCHA v3 | 120 seconds | Timer starts when execute() resolves |
| reCAPTCHA Enterprise | 120 seconds | Same window, stricter validation |
What happens when a token expires
# Expired token validation response from Google
{
"success": False,
"error-codes": ["timeout-or-duplicate"]
}
The timeout-or-duplicate error code covers both expiration and reuse attempts. Google intentionally does not distinguish between the two cases.
Timing implications for API solvers
When using an API solver like CaptchaAI, the timeline becomes:
Your code extracts site key and page URL
↓ ~0 seconds
Submit to CaptchaAI API (in.php)
↓ ~15-45 seconds (solver working)
Receive token from CaptchaAI API (res.php)
↓
⏱ 120-SECOND COUNTDOWN STARTS HERE
↓
Your code submits token to target website
↓ Must happen within remaining time
Target website validates token with Google
Critical insight: The 120-second timer does NOT start when you submit to CaptchaAI. It starts when CaptchaAI generates the token. You have the remaining time (120 seconds minus any delay in polling) to submit the token.
Safe timing strategy
import time
# Record when you receive the token
token_received_at = time.time()
# ... prepare form submission ...
# Check if token is still valid (leave 10-second safety margin)
elapsed = time.time() - token_received_at
if elapsed > 110: # 120 - 10 second margin
print("Token too old, requesting new one")
# Re-submit to CaptchaAI for a fresh token
else:
print(f"Token age: {elapsed:.1f}s — submitting")
# Submit form with token
Stage 3: Server-side validation
Google siteverify API
After receiving the token, your server (or the target website's server) validates it with Google:
import requests
def validate_recaptcha_token(token, secret_key, remote_ip=None):
"""Validate a reCAPTCHA token with Google's siteverify API."""
payload = {
"secret": secret_key,
"response": token,
}
if remote_ip:
payload["remoteip"] = remote_ip
result = requests.post(
"https://www.google.com/recaptcha/api/siteverify",
data=payload,
timeout=10,
).json()
return result
Validation response (v2)
{
"success": true,
"challenge_ts": "2025-01-15T10:30:00Z",
"hostname": "example.com"
}
Validation response (v3)
{
"success": true,
"score": 0.9,
"action": "login",
"challenge_ts": "2025-01-15T10:30:00Z",
"hostname": "example.com"
}
Error codes
| Error code | Meaning | Common cause |
|---|---|---|
missing-input-secret |
Secret key not provided | Server-side configuration error |
invalid-input-secret |
Secret key is wrong | Wrong key for this site |
missing-input-response |
Token not provided | Form field not submitted |
invalid-input-response |
Token is malformed | Corruption during transmission |
bad-request |
Request invalid | Malformed POST data |
timeout-or-duplicate |
Token expired or already used | Too slow to submit, or double-submit |
Stage 4: Token consumption (one-time use)
A reCAPTCHA token can be verified exactly once. After Google's siteverify API validates it, the token is consumed and cannot be reused.
# First validation — succeeds
result1 = validate_recaptcha_token(token, secret)
# { "success": true, "score": 0.9, ... }
# Second validation — fails (same token)
result2 = validate_recaptcha_token(token, secret)
# { "success": false, "error-codes": ["timeout-or-duplicate"] }
Implications for automation
- You cannot reuse a token across multiple form submissions
- Each form submission needs a fresh token
- If a submission fails for non-CAPTCHA reasons (network error, validation error), you need a new token to retry
- Parallelizing form submissions requires parallel token generation
Complete token management in Python
import requests
import time
class RecaptchaTokenManager:
"""Manage reCAPTCHA token lifecycle for automation workflows."""
API_KEY = "YOUR_API_KEY"
MAX_TOKEN_AGE = 110 # seconds (120 minus 10-second safety margin)
def __init__(self, site_key, page_url, version="v2"):
self.site_key = site_key
self.page_url = page_url
self.version = version
def request_token(self):
"""Request a new token from CaptchaAI."""
params = {
"key": self.API_KEY,
"method": "userrecaptcha",
"googlekey": self.site_key,
"pageurl": self.page_url,
"json": 1,
}
if self.version == "v3":
params["version"] = "v3"
params["action"] = "submit"
params["min_score"] = "0.9"
submit = requests.post(
"https://ocr.captchaai.com/in.php",
data=params,
).json()
if submit.get("status") != 1:
raise Exception(f"Submit failed: {submit}")
return submit["request"]
def poll_token(self, task_id, timeout=180):
"""Poll for token result with timestamp tracking."""
start = time.time()
for _ in range(60):
time.sleep(5)
if time.time() - start > timeout:
raise TimeoutError("Token solve timeout")
result = requests.get(
"https://ocr.captchaai.com/res.php",
params={
"key": self.API_KEY,
"action": "get",
"id": task_id,
"json": 1,
},
).json()
if result.get("status") == 1:
return {
"token": result["request"],
"received_at": time.time(),
}
raise TimeoutError("Token solve timeout")
def get_fresh_token(self):
"""Get a fresh, immediately usable token."""
task_id = self.request_token()
result = self.poll_token(task_id)
return result
def is_token_valid(self, token_data):
"""Check if a token is still within the valid time window."""
age = time.time() - token_data["received_at"]
return age < self.MAX_TOKEN_AGE
def submit_with_token(self, submit_fn):
"""
Get a token and submit immediately.
submit_fn receives the token string and returns True/False.
"""
token_data = self.get_fresh_token()
if not self.is_token_valid(token_data):
raise Exception("Token expired before submission")
success = submit_fn(token_data["token"])
if not success:
# Token is consumed even on failed submission — get a new one
print("Submission failed, requesting new token...")
token_data = self.get_fresh_token()
success = submit_fn(token_data["token"])
return success
# Usage
manager = RecaptchaTokenManager(
site_key="6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
page_url="https://example.com/login",
version="v2",
)
def my_form_submit(token):
response = requests.post("https://example.com/login", data={
"username": "user",
"password": "pass",
"g-recaptcha-response": token,
})
return response.status_code == 200
result = manager.submit_with_token(my_form_submit)
print(f"Submission {'succeeded' if result else 'failed'}")
Token troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
timeout-or-duplicate every time |
Token taking too long to submit | Reduce time between receive and submit |
timeout-or-duplicate intermittently |
Token reuse or race condition | Ensure each submission gets a fresh token |
invalid-input-response |
Token corrupted in transit | Check URL encoding, form field name |
| Score always 0.1 (v3) | Mismatched action parameter | Match the action value to what the page expects |
hostname mismatch |
Token for wrong domain | Ensure pageurl matches the target domain exactly |
Frequently asked questions
How long do reCAPTCHA tokens last?
All reCAPTCHA token versions expire after 120 seconds (2 minutes). In practice, submit within 90 seconds to account for clock differences and network latency.
Can I extend a reCAPTCHA token's lifetime?
No. The expiration is enforced server-side by Google and cannot be extended. If your workflow takes longer than 120 seconds between token generation and form submission, you must request a new token.
Why does Google return "timeout-or-duplicate" for both expired and reused tokens?
Google intentionally uses a single error code for both cases to prevent attackers from distinguishing between an expired token (timing issue) and a consumed token (reuse attempt). From a security perspective, both should be treated the same way: request a new token.
Does CaptchaAI's solve time eat into the 120-second window?
The 120-second countdown starts when the token is generated by the solver, not when you submit the request to CaptchaAI. Typically, CaptchaAI solves within 15-45 seconds, leaving 75-105 seconds for your code to submit the token. This is sufficient for most workflows.
Summary
The reCAPTCHA token lifecycle has four stages: generation (challenge completion), expiration (120-second window), server validation (Google siteverify API), and consumption (one-time use). For automation workflows using CaptchaAI, the critical factor is submitting the token within the 120-second window. Use the token manager pattern above to track token age and automatically request fresh tokens when needed.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.