O erro timeout-or-duplicate é a falha reCAPTCHA mais comum em fluxos de trabalho de automação. Isso significa que seu token expirou antes do envio ou foi usado duas vezes. Com os tokens reCAPTCHA durando apenas 120 segundos e os solucionadores de API levando de 15 a 45 segundos para gerá-los, o gerenciamento do tempo é fundamental. Este guia aborda a mecânica exata de expiração, condições comuns de corrida e estratégias práticas de tempo.
A regra dos 120 segundos
Cada token reCAPTCHA expira exatamente 120 segundos após a geração. Isso se aplica a todas as versões:
Token generated (solve complete)
├─── 0s: Token is valid ✓
├─── 60s: Token is valid ✓
├─── 110s: Token is valid ✓ (but cutting it close)
├─── 119s: Token is valid ✓ (dangerous territory)
└─── 120s: Token EXPIRED ✗ (timeout-or-duplicate)
Onde o cronômetro começa
| Cenário | O cronômetro inicia quando... |
|---|---|
| Caixa de seleção reCAPTCHA v2 | O usuário marca a caixa de seleção (desafio resolvido) |
| Grade de imagem reCAPTCHA v2 | O usuário conclui a rodada final de seleção de imagens |
| reCAPTCHA v2 invisível | O retorno de chamada execute() é acionado com token |
| reCAPTCHA v3 | execute() Promise resolve com token |
| Solucionador de API (CaptchaAI) | O Solver gera o token (NÃO quando você o recebe) |
A lacuna de tempo do solucionador
Ao usar um solucionador de API, há um atraso entre o momento em que o solucionador gera o token e o momento em que seu código o recebe:
Solver generates token (timer starts)
↓ ~1-5 seconds (network + polling interval)
Your code receives token via res.php poll
↓ You now have ~115-119 seconds remaining
Your code processes and submits token
↓ ~1-10 seconds (depends on your workflow)
Target website validates token with Google
↓ ~1-2 seconds (Google API response time)
Total remaining after validation: ~105-117 seconds (comfortable)
Na prática, a janela de 120 segundos oferece tempo suficiente para o envio do seu código imediatamente após o recebimento do token. Os problemas ocorrem quando:
- Seu código tem etapas de processamento adicionais entre o recebimento do token e o envio dele
- Vários formulários precisam ser preenchidos antes do envio
- O site de destino tem um tempo de resposta lento
- Você enfileira tokens e os envia mais tarde
Condições comuns de corrida
Condição de corrida 1: resolução paralela + envio sequencial
# WRONG: Solving multiple CAPTCHAs in parallel, then submitting sequentially
tokens = []
for url in urls:
task_id = solve_captcha(url) # All submitted at t=0
tokens.append(task_id)
# All tokens arrive around t=30
solved_tokens = [poll_result(tid) for tid in tokens]
# Sequential submission: first token at t=32, last at t=120+
for i, (url, token) in enumerate(zip(urls, solved_tokens)):
submit_form(url, token) # Later tokens may be expired!
time.sleep(10) # Each wait adds pressure
Correção: Resolva e envie cada CAPTCHA antes de iniciar o próximo:
# CORRECT: Solve and submit one at a time
for url in urls:
token = solve_and_wait(url) # Token received at t=30
submit_form(url, token) # Submitted at t=31 (89 seconds remaining)
Condição de corrida 2: pré-busca de token
# WRONG: Pre-fetching tokens before knowing when they'll be used
token = solve_captcha() # Token received at t=0
# ... user fills out form (30-120+ seconds) ...
# ... validation checks ...
# ... other processing ...
submit_form(token) # Token may be expired!
Correção: Resolva o CAPTCHA como última etapa antes do envio:
# CORRECT: Late-bind the CAPTCHA solve
prepare_form_data() # Do everything that doesn't need the token
validate_inputs() # Run validation before spending a token
# Now solve and submit immediately
token = solve_captcha() # Token received at t=0
submit_form(token) # Submitted at t=1 (119 seconds remaining)
Condição de corrida 3: envio de formulário em várias etapas
# PROBLEMATIC: Multi-step form where CAPTCHA is on step 1 but submit is step 3
token = solve_captcha() # t=0: Token received
fill_step_1(token) # t=5: Step 1 submitted
response = fill_step_2() # t=15: Step 2 completed
# ... step 2 has additional verification ...
wait_for_verification() # t=60: Verification complete
fill_step_3_and_submit() # t=65: Final submission (55 seconds remaining - OK)
# BUT if step 2 takes longer than expected...
Correção: Meça a idade do token e resolva se necessário:
token_received_at = time.time()
token = solve_captcha()
token_received_at = time.time()
# ... multi-step process ...
# Before final submission, check token age
token_age = time.time() - token_received_at
if token_age > 100: # 20-second safety margin
print(f"Token is {token_age:.0f}s old — requesting fresh token")
token = solve_captcha()
token_received_at = time.time()
submit_final(token)
Gerenciador de tempo de token
Uma classe pronta para produção para gerenciar a vida útil do token:
import time
import requests
class TokenTimingManager:
"""Manage reCAPTCHA token timing to prevent expiration errors."""
API_KEY = "YOUR_API_KEY"
TOKEN_LIFETIME = 120
SAFETY_MARGIN = 15 # seconds before expiry to consider "stale"
def __init__(self, site_key, page_url, version="v2"):
self.site_key = site_key
self.page_url = page_url
self.version = version
self.current_token = None
self.token_timestamp = None
def _solve(self):
"""Request and poll for a new token."""
params = {
"key": self.API_KEY,
"method": "userrecaptcha",
"googlekey": self.site_key,
"pageurl": self.page_url,
"json": 1,
}
if self.version == "v3":
params.update({"version": "v3", "action": "submit", "score_qa": "0.9"})
submit = requests.post("https://ocr.captchaai.com/in.php", data=params).json()
task_id = submit["request"]
for _ in range(60):
time.sleep(5)
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:
self.current_token = result["request"]
self.token_timestamp = time.time()
return self.current_token
raise TimeoutError("Token solve timeout")
@property
def token_age(self):
"""Seconds since current token was received."""
if self.token_timestamp is None:
return float("inf")
return time.time() - self.token_timestamp
@property
def token_remaining(self):
"""Seconds remaining before token expires."""
return max(0, self.TOKEN_LIFETIME - self.token_age)
@property
def is_fresh(self):
"""Whether the token is fresh enough to use."""
return self.token_remaining > self.SAFETY_MARGIN
def get_token(self):
"""Get a valid token, solving if current is stale or missing."""
if self.current_token and self.is_fresh:
return self.current_token
return self._solve()
def use_token(self):
"""Get and consume a token (cannot be reused)."""
token = self.get_token()
# Mark as consumed
self.current_token = None
self.token_timestamp = None
return token
# Usage
manager = TokenTimingManager(
site_key="6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
page_url="https://staging.example.com/qa-login",
)
# Get a fresh token right before submission
token = manager.use_token()
print(f"Token remaining: {manager.TOKEN_LIFETIME}s (fresh solve)")
# Submit form with token...
Lidando com o erro de tempo limite ou duplicado
Ao receber timeout-or-duplicate, diagnostique a causa:
def handle_recaptcha_error(error_codes, token_age_seconds):
"""Diagnose and handle reCAPTCHA validation errors."""
if "timeout-or-duplicate" in error_codes:
if token_age_seconds > 120:
return {
"cause": "Token expired (age: {:.0f}s > 120s)".format(token_age_seconds),
"fix": "Reduce time between receiving and submitting token",
"action": "re-solve",
}
elif token_age_seconds < 5:
return {
"cause": "Token likely reused (duplicate submission)",
"fix": "Ensure each form submission gets a unique token",
"action": "re-solve",
}
else:
return {
"cause": "Token may have been reused or server-side timing issue",
"fix": "Check for double-submit in form handler",
"action": "re-solve",
}
if "invalid-input-response" in error_codes:
return {
"cause": "Token is malformed or corrupted",
"fix": "Check token transmission (URL encoding, field name)",
"action": "re-solve",
}
return {"cause": "Unknown", "action": "investigate"}
Práticas recomendadas de tempo
| Pratique | Recomendação |
|---|---|
| Lacuna entre resolver e enviar | Mantenha menos de 90 segundos (margem de 30 segundos) |
| Intervalo da pesquisa | 5 segundos entre verificações res.php |
| validação antecipada em staging | Somente pré-resolva se você souber que o envio acontecerá em 60 segundos |
| Tente novamente quando expirar | Sempre solicite um token novo em timeout-or-duplicate |
| Fila de tokens | Nunca coloque tokens na fila – cada um expira independentemente |
| Operações paralelas | Resolva por tarefa, não resolva tudo e depois use |
| Monitoramento | Rastreie a idade do token no momento do envio |
Perguntas frequentes
Posso estender a janela do token de 120 segundos?
Não. A expiração é imposta no servidor pelo Google e não pode ser prorrogada de forma alguma. Os 120 segundos são um limite rígido.
O atraso na votação reduz meu tempo de token utilizável?
Sim, um pouco. Se você pesquisar a cada 5 segundos, poderá receber o token até 5 segundos após ele ter sido gerado. Na prática, isso significa que você tem aproximadamente 115 segundos em vez de 120. Isso geralmente não é um problema.
Devo validação antecipada em staging os tokens para tê-los prontos?
Somente se você puder garantir que o token será enviado dentro de 60 a 90 segundos. A validação antecipada em staging é útil para fluxos de trabalho sensíveis à latência (por exemplo, compras competitivas), onde os 15 a 45 segundos extras de tempo de resolução são importantes. Para a maioria dos fluxos de trabalho, a solução sob demanda é mais segura.
Por que o Google usa o mesmo erro para tokens expirados e duplicados?
O Google oculta intencionalmente a distinção para evitar que scripts de automação usem o código de erro para detectar qual caso ocorreu. Ambos os casos têm o mesmo remédio: solicitar um novo token.
Resumo
Os tokens reCAPTCHA expiram após exatamente 120 segundos e só podem ser usados uma vez. O erro de automação mais comum — timeout-or-duplicate — é causado pelo envio de tokens tarde demais ou pela reutilização deles. Ao usarCaptchaAI, resolva os tokens antes do envio, rastreie a idade dos tokens e nunca coloque-os na fila ou reutilize-os. Use o padrão do gerenciador de tempo acima para lidar automaticamente com a expiração e a resolução em fluxos de trabalho de produção.
Artigos relacionados
- Como resolver o retorno de chamada do Recaptcha V2 usando API
- Torniquete Recaptcha V2 no mesmo local
- Ciclo de vida do token Recaptcha explicado