Solución de Problemas

Caducidad del token reCAPTCHA: ventanas de tiempo y condiciones de carrera

El error timeout-or-duplicate es el error reCAPTCHA más común en los flujos de trabajo de automatización. Significa que su token expiró antes de enviarlo o se usó dos veces. Dado que los tokens reCAPTCHA duran solo 120 segundos y los solucionadores de API tardan entre 15 y 45 segundos en generarlos, la gestión del tiempo es fundamental. Esta guía cubre la mecánica de vencimiento exacta, las condiciones de carrera comunes y las estrategias prácticas de cronometraje.


La regla de los 120 segundos

Cada token reCAPTCHA caduca exactamente 120 segundos después de su generación. Esto se aplica a todas las versiones:

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)

Donde comienza el cronómetro

Escenario El cronómetro comienza cuando...
Casilla de verificación reCAPTCHA v2 El usuario marca la casilla de verificación (desafío resuelto)
Cuadrícula de imágenes reCAPTCHA v2 El usuario completa la ronda final de selección de imágenes.
reCAPTCHA v2 invisible La devolución de llamada execute() se activa con el token
reCAPTCHA v3 execute() La promesa se resuelve con el token
Solucionador API (CaptchaAI) Solver genera el token (NO cuando lo recibes)

La brecha de tiempo del solucionador

Cuando se utiliza un solucionador de API, hay un retraso entre el momento en que el solucionador genera el token y el momento en que el código lo recibe:

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)

En la práctica, la ventana de 120 segundos proporciona tiempo suficiente para que su código se envíe inmediatamente después de recibir el token. Los problemas ocurren cuando:

  • Su código tiene pasos de procesamiento adicionales entre recibir el token y enviarlo.
  • Es necesario completar varios formularios antes del envío.
  • El sitio web de destino tiene un tiempo de respuesta lento.
  • Pones en cola los tokens y los envías más tarde.

Condiciones de carrera comunes

Condición de carrera 1: resolución paralela + envío secuencial

# 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

Solución: Resuelva y envíe cada CAPTCHA antes de comenzar el siguiente:

# 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)

Condición de carrera 2: búsqueda previa de tokens

# 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!

Solución: Resuelva el CAPTCHA como último paso antes del envío:

# 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)

Condición de carrera 3: envío de formulario de varios pasos

# 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...

Solución: Mide la antigüedad del token y vuelve a resolverlo si es necesario:

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)

Administrador de sincronización de tokens

Una clase lista para producción para gestionar la vida útil de los tokens:

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", "min_score": "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...

Manejo del error de tiempo de espera o duplicado

Cuando reciba timeout-or-duplicate, diagnostique la 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"}

Mejores prácticas de sincronización

Practica Recomendación
Brecha entre resolver y enviar Manténgase por debajo de 90 segundos (margen de 30 segundos)
Intervalo de consulta 5 segundos entre comprobaciones de res.php
Preparación controlada Solo prepare un token si sabe que el envío de prueba se realizará dentro de 60 segundos.
Reintentar al vencimiento Solicite siempre un token nuevo en timeout-or-duplicate
Cola de tokens Nunca pongas en cola tokens: cada uno caduca de forma independiente
Operaciones paralelas Resolver por tarea, no resolverlo todo y luego usarlo
Monitoreo Seguimiento de la antigüedad del token en el momento del envío

Preguntas frecuentes

¿Puedo ampliar la ventana del token de 120 segundos?

No. Google aplica la caducidad en el servidor y no puede ampliarse de ningún modo. Los 120 segundos son un límite estricto.

¿El retraso en el sondeo reduce el tiempo de uso de mi token?

Sí, un poco. Si realiza una consulta cada 5 segundos, puede recibir el token hasta 5 segundos después de su generación. En la práctica, esto significa que tienes ~115 segundos en lugar de 120. Esto normalmente no es un problema.

¿Debo resolver previamente los tokens para tenerlos listos?

Solo si el token se enviará dentro de 60 a 90 segundos en una prueba controlada. Para la mayoría de los flujos de trabajo, la resolución bajo demanda es más segura y más fácil de diagnosticar.

¿Por qué Google utiliza el mismo error para tokens caducados y duplicados?

Google oculta intencionalmente la distinción para evitar que los scripts de automatización utilicen el código de error para detectar qué caso ocurrió. Ambos casos tienen el mismo remedio: solicitar un nuevo token.


Resumen

Los tokens reCAPTCHA caducan después de exactamente 120 segundos y solo se pueden usar una vez. El error de automatización más común, timeout-or-duplicate, se debe a que los tokens se envían demasiado tarde o se reutilizan. Al usarCaptchaAI, resuelva los tokens justo antes de enviarlos, realice un seguimiento de la antigüedad de los tokens y nunca los ponga en cola ni los reutilice. Utilice el patrón de administrador de tiempos anterior para manejar automáticamente la caducidad y la resolución en los flujos de trabajo de producción.

Artículos relacionados

Los comentarios están deshabilitados para este artículo.