Solución de Problemas

Token de Turnstile no válido tras resolverlo: causas y solución

Un token de Turnstile rechazado casi nunca significa que el CAPTCHA se resolviera mal: significa que llegó tarde, salió de un widget que no era el del formulario, viajó en un campo con otro nombre o se envió desde una sesión distinta a la que cargó la página. Esas cuatro situaciones explican la mayoría de los 403 que aparecen tras un status: 1 correcto.

La distinción importa porque cambia dónde miras: si la resolución falla, la API devuelve un código ERROR_*; si el token se resuelve y el sitio lo rechaza, tu log de CaptchaAI se ve impecable y el fallo solo aparece en la verificación contra Cloudflare. Estas son las causas por orden de frecuencia.


Triaje rápido: qué te está diciendo el síntoma

Lo que observas Causa más probable
403 del sitio justo después de enviar el formulario El token caducó antes de llegar al backend
El formulario se envía y no ocurre nada El token viaja en un campo con otro nombre
El token se acepta pero la acción queda bloqueada Sitekey de otro widget de la misma página
Funciona el primer intento y falla el reintento El token ya se consumió: es de un solo uso
En el navegador funciona, en el script no Faltan cookies de sesión o cabeceras

Un token de Turnstile, un envío: la regla de los reintentos

Los tokens de Turnstile son de un solo uso: en cuanto el backend los valida contra Cloudflare, quedan consumidos. El primer intento pasa, el reintento reenvía el mismo cf-turnstile-response y devuelve 403; como el primero funcionó, es fácil culpar a la red.

Corrección: resuelve un CAPTCHA nuevo dentro del bucle de reintento, nunca fuera.


El token caducó: 300 segundos que se te van en el sondeo

Un token de Turnstile suele caducar a los 300 segundos (5 minutos) y algunas implementaciones acortan ese margen. CaptchaAI resuelve Turnstile en menos de 10 segundos con una alta tasa de éxito, así que el tiempo perdido está en lo que hace tu script después: un sleep generoso o una cola interna bastan para que el token llegue muerto.

Corrección: envía el token en el mismo bloque en el que lo recibes.

import requests
import time

API_KEY = "YOUR_API_KEY"

# Submit Turnstile task
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": "0x4AAAAAAADnPIDROz1234",
    "pageurl": "https://staging.example.com/qa-login",
    "json": 1
}).json()

task_id = submit["request"]
time.sleep(10)

# Poll for result
for _ in range(24):
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": 1
    }).json()
    if result.get("status") == 1:
        token = result["request"]
        # USE TOKEN IMMEDIATELY — do not delay
        response = requests.post("https://staging.example.com/qa-login", data={
            "username": "user",
            "password": "pass",
            "cf-turnstile-response": token
        })
        break
    time.sleep(5)

Si separas resolución y envío en dos procesos, mide el tiempo entre ambos puntos: pasadas unas decenas de segundos, descarta el token y pide otro.


Sitekey de Turnstile equivocado: resolviste el widget que no era

Cada widget de Turnstile tiene su propio sitekey. En páginas con varios formularios es fácil quedarse con el primero del HTML: el token sale válido, pero válido para otro widget.

Cómo obtener el sitekey correcto:

// In browser console on the target page
document.querySelectorAll('[data-sitekey]').forEach(el => {
    console.log('Sitekey:', el.getAttribute('data-sitekey'));
    console.log('Element:', el);
});

Si aparece más de uno, usa el que cuelga del formulario que vas a enviar y guárdalo junto al código de tus tests: un sitekey desactualizado tras un rediseño da el mismo síntoma.


El nombre del campo: cf-turnstile-response y solo ese

Turnstile espera el token en cf-turnstile-response. Es el fallo típico al portar código que resolvía reCAPTCHA, donde el campo se llama g-recaptcha-response.

# WRONG — this is for reCAPTCHA
data = {"g-recaptcha-response": token}

# CORRECT — this is for Turnstile
data = {"cf-turnstile-response": token}

Algunos sitios renombran el campo. Antes de darlo por hecho, comprueba cuál rellena el widget en la página:

// Check what field the Turnstile widget populates
document.querySelector('[name*="turnstile"], [name*="cf-"]')

Con un nombre personalizado, envía el token solo en ese campo: duplicarlo puede hacer que el backend valide el vacío.


Cookies y sesión: resolver en un contexto y enviar en otro

Cloudflare puede validar el token contra las cookies de la sesión que cargó la página. Si abres con un cliente y envías con otro, la verificación falla aunque el token sea reciente. Es el clásico "en el navegador funciona".

# Use the SAME session for page load and token submission
session = requests.Session()

# Load the page first to establish cookies
session.get("https://staging.example.com/qa-login")

# Then solve and submit using the same session
token = solve_turnstile(sitekey, pageurl)
session.post("https://staging.example.com/qa-login", data={
    "cf-turnstile-response": token
})

Con Selenium o Playwright, inyecta el token en el DOM de la pestaña que ya tiene las cookies en vez de lanzar una solicitud aparte.


action y cData: cuando el token va ligado a la operación

Algunas integraciones añaden los parámetros action y cData, que quedan vinculados al token emitido. Si el sitio los espera y resuelves sin ellos, la verificación lo rechaza sin dar más pistas.

submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": "0x4AAAAAAADnPIDROz1234",
    "pageurl": "https://staging.example.com/qa-login",
    "action": "login",           # If required by the site
    "data": "custom_cdata_value", # If required by the site
    "json": 1
}).json()

Revisa el HTML del widget (data-action, data-cdata) y pasa esos mismos valores en la llamada a la API.


Árbol de decisión: cinco preguntas en orden

Token solved but rejected
    ↓
Used within 5 minutes? → No → Solve again, submit immediately
    ↓ Yes
Correct sitekey? → No → Find the correct sitekey from the page
    ↓ Yes
Using cf-turnstile-response field? → No → Change field name
    ↓ Yes  
Same session for page load + submit? → No → Use session persistence
    ↓ Yes
Token used only once? → No → Solve a new token per submission
    ↓ Yes
Site requires action/cData? → Check page source, add to API call

Recórrelo de arriba abajo y detente en la primera respuesta negativa: casi todo se cierra en los tres primeros nodos.


Iguala el navegador entre tu máquina y CI

Cuando el mismo test pasa en local y falla en CI, la diferencia suele estar en el navegador. Usa una única función de arranque para QA, staging y CI:

from selenium import webdriver

def make_driver(headless: bool = True) -> webdriver.Chrome:
    options = webdriver.ChromeOptions()
    if headless:
        options.add_argument('--headless=new')
    options.add_argument('--window-size=1280,800')
    options.add_argument('--lang=es-ES')
    return webdriver.Chrome(options=options)

Piensa en una agencia de Bogotá que mantiene el portal de trámites de un cliente: el equipo prueba en local con el navegador visible y CI lo ejecuta headless en un contenedor. Si el viewport cambia, el widget se renderiza en otra posición y el test recoge el sitekey del formulario equivocado. El síntoma es "el token no vale"; la causa está tres pasos antes.


Cómo encaja CaptchaAI en un pipeline propio

El patrón es el mismo con cualquier lenguaje o framework de pruebas:

  1. Tu test envía a CaptchaAI los datos públicos del widget de tu propia aplicación: sitekey, URL de la página y tipo de CAPTCHA.
  2. CaptchaAI devuelve un token válido para esa página.
  3. Tu test lo inyecta en el campo correspondiente y tu backend lo verifica igual que si viniera de una persona.

Este flujo aplica a integraciones que controlas. Revisa los términos de servicio y la normativa de protección de datos aplicable antes de automatizar flujos con datos personales.


Qué medir para no depurar a ciegas

Instrumenta los pasos de CAPTCHA como cualquier dependencia externa. Tres señales bastan:

  • Tiempo entre token recibido y token enviado — la métrica que delata las caducidades.
  • Tasa de éxito por endpoint propio — verificaciones del backend que pasan sobre el total de intentos.
  • Distribución de errores — agrupada por código (ERROR_*, tiempos de espera, fallos de red).

Y antes de dar la integración por buena: prueba sobre entornos propios o autorizados, usa una clave API separada para QA y versiona la configuración del widget (sitekey, action) junto al código de los tests.


Preguntas frecuentes

¿Puedo reutilizar el mismo token en varios envíos?

No. Se consume en la primera verificación contra Cloudflare, así que la llamada a la API tiene que vivir dentro del bucle de reintento.

¿Cuánto margen tengo para enviar el token?

Unos 300 segundos como referencia, menos si el sitio acorta la caducidad. Trátalo como un presupuesto y pide otro token si te pasas.

¿Necesito un navegador headless para que el token sea aceptado?

No. Basta un cliente HTTP si reutilizas la sesión que cargó la página; el navegador headless solo hace falta si el envío depende de JavaScript.

¿Qué significa invalid-input-response en la respuesta del backend?

Que Cloudflare recibió un token que no puede validar: caducado, ya usado o emitido para otro sitekey. Recorre el árbol de decisión en ese orden antes de tocar nada más.

¿Cuánto cuesta resolver Turnstile a volumen?

CaptchaAI cobra por thread concurrente, no por resolución: cada plan incluye resoluciones ilimitadas por thread. BASIC ($15/mes, 5 threads) cubre una suite de QA pequeña y ADVANCE ($90/mes, 50 threads) da margen a varios runners en paralelo. Para equipos que facturan en moneda local, el coste fijo en USD es más previsible que el pago por resolución.


Resuelve Turnstile con CaptchaAI

Crea tu cuenta en captchaai.com, obtén tu clave API y valida el flujo completo en staging antes de llevarlo a CI.


Guías relacionadas

Valida tus integraciones de CAPTCHA en entornos propios con CaptchaAI.

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