El sitekey de Cloudflare Turnstile es una cadena pública (suele empezar por 0x4AAA…) que el propio sitio incrusta en su HTML: no es un secreto y siempre está a la vista si sabes dónde mirar. Ese es todo el reto de este tutorial, porque para resolver Turnstile con CaptchaAI solo necesitas dos datos: el sitekey y la URL de la página donde aparece el widget. Abajo tienes los tres lugares donde vive el sitekey, el orden en que conviene buscarlo y el código para enviarlo a la API y recuperar el token.
Sitekey frente a token: no los confundas
Antes de empezar, conviene separar dos valores que se mezclan a menudo:
- El sitekey identifica al sitio ante Cloudflare. Es público, fijo y el mismo en cada carga. Es lo que tú extraes de la página.
- El token es el resultado de resolver el CAPTCHA. Es de un solo uso, caduca en pocos minutos y es lo que CaptchaAI te devuelve para que lo inyectes en el formulario.
Dicho de otro modo: el sitekey es la entrada del proceso y el token, la salida.
Dónde vive el sitekey y en qué orden buscarlo
Turnstile expone el sitekey en tres puntos de la página. No necesitas los tres: para cualquier sitio, al menos uno funciona. Este es el orden que ahorra tiempo:
- El atributo
data-sitekeyen los elementos.cf-turnstile. Es el más rápido y no requiere ejecutar JavaScript; pruébalo siempre primero. - La llamada
turnstile.render()en el código de la página. Muchos sitios montan el widget por programación y pasan el sitekey como parámetro. - La URL del
srcdel iframe de Turnstile. Es el recurso más fiable: cuando el widget ya se ha renderizado, el sitekey aparece siempre en esa URL.
Si el HTML estático no muestra ninguno, vuelve a cargar la página con un navegador headless (Selenium o Puppeteer) y repite la búsqueda: el widget necesita JavaScript para montarse.
Método 1: leer el atributo data-sitekey del DOM
La vía más directa. En la consola del navegador, recorre los elementos .cf-turnstile y vuelca sus atributos data-*:
// Browser console
document.querySelectorAll('.cf-turnstile').forEach((el, i) => {
console.log(`Turnstile ${i}:`, {
sitekey: el.getAttribute('data-sitekey'),
action: el.getAttribute('data-action'),
cData: el.getAttribute('data-cdata'),
theme: el.getAttribute('data-theme'),
});
});
Python con HTML estático
Si el widget viene en la respuesta HTML sin depender de JavaScript, una expresión regular sobre el texto de la página basta:
import re
import requests
html = requests.get("https://staging.example.com/qa-login").text
matches = re.findall(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
html
)
for sk in matches:
print(f"Sitekey: {sk}")
Python con Selenium
Cuando el sitekey solo aparece después de que el navegador ejecute los scripts, carga la página con Selenium y lee el atributo directamente del elemento:
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
widgets = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for w in widgets:
sitekey = w.get_attribute("data-sitekey")
action = w.get_attribute("data-action")
print(f"Sitekey: {sitekey}, Action: {action}")
Método 2: interceptar la llamada turnstile.render()
Algunos sitios no dejan el sitekey en el HTML: lo pasan como parámetro a turnstile.render() en tiempo de ejecución. El patrón se ve así:
turnstile.render('#captcha-container', {
sitekey: '0x4AAAAAAAB...',
callback: function(token) {
document.getElementById('cf-token').value = token;
},
});
Puedes recuperarlo con una expresión regular sobre el código fuente de la página:
# Find turnstile.render calls
render_match = re.search(
r'turnstile\.render\s*\([^,]*,\s*\{([^}]+)\}',
html
)
if render_match:
config = render_match.group(1)
sk = re.search(r'sitekey\s*:\s*["\']([^"\']+)', config)
if sk:
print(f"Sitekey from render: {sk.group(1)}")
Intercepción con Puppeteer
Si el sitekey se construye de forma dinámica y la regex no lo captura, envuelve turnstile.render antes de que la página cargue y registra cada parámetro que recibe:
// Intercept turnstile.render before page loads
await page.evaluateOnNewDocument(() => {
window.__turnstileParams = [];
const origRender = window.turnstile?.render;
Object.defineProperty(window, 'turnstile', {
set(val) {
this._turnstile = val;
const orig = val.render;
val.render = function(container, params) {
window.__turnstileParams.push(params);
console.log('Turnstile render:', JSON.stringify(params));
return orig.apply(this, arguments);
};
},
get() { return this._turnstile; }
});
});
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);
Método 3: extraer el sitekey del src del iframe
Cuando Turnstile termina de montarse, genera un iframe servido desde challenges.cloudflare.com. El sitekey viaja en su src, así que basta con leerlo:
document.querySelectorAll('iframe').forEach(iframe => {
if (iframe.src.includes('challenges.cloudflare.com')) {
console.log('Turnstile iframe:', iframe.src);
const match = iframe.src.match(/sitekey=([A-Za-z0-9_-]+)/);
if (match) console.log('Sitekey:', match[1]);
}
});
Resolver Turnstile con CaptchaAI: envío y sondeo
Con el sitekey y la URL de la página, el flujo con CaptchaAI son dos pasos:
- Envío: mandas la tarea al endpoint
in.phpconmethod=turnstile. - Sondeo: consultas
res.phpcada 5 segundos hasta que devuelve el token.
Turnstile normalmente se resuelve en menos de 10 segundos con una alta tasa de éxito. El patrón es el mismo tanto si automatizas las pruebas de tu propio checkout como si monitorizas un portal público protegido por Turnstile —un portal de cita previa o un marketplace regional del tipo MercadoLibre—; respeta siempre los términos de servicio del sitio y la normativa de protección de datos aplicable.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://staging.example.com/qa-login"
# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "turnstile",
"sitekey": SITEKEY,
"pageurl": PAGE_URL,
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
# Poll
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
token = result["request"]
print(f"Turnstile token: {token[:50]}...")
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result['request']}")
JavaScript
const axios = require('axios');
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'turnstile',
sitekey: '0x4AAAAAAAB...',
pageurl: 'https://staging.example.com/qa-login',
json: 1,
}
});
const taskId = submit.data.request;
// Poll for result
let token = null;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) {
token = poll.data.request;
break;
}
}
console.log(`Token: ${token.substring(0, 50)}...`);
CaptchaAI factura por threads concurrentes, no por resolución: cada thread es un CAPTCHA en curso y, al terminar, queda libre para el siguiente. Para probar Turnstile, el plan BASIC ($15/mes, 5 threads) sobra; cuando crezca tu volumen puedes subir a STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads) sin tocar el código.
Solución de problemas frecuentes
Si la extracción o la resolución fallan, casi siempre es por uno de estos motivos:
| Problema | Causa | Solución |
|---|---|---|
No se encontró ningún elemento .cf-turnstile |
Renderizado dinámicamente | Espera a que se cargue la página o usa MutationObserver |
| Sitekey vacío | Configurado mediante la API de JavaScript | Busca turnstile.render en los scripts |
| Token rechazado | URL de página o sitekey incorrectos | Verifica que ambos valores coincidan con el sitio de destino |
Parámetro method incorrecto |
Usar userrecaptcha para Turnstile |
Usa method=turnstile |
Inyectar el token en la página
Resolver el CAPTCHA es solo la mitad del trabajo: el token todavía tiene que llegar al formulario. Turnstile guarda su valor en un input oculto llamado cf-turnstile-response. Además de rellenarlo, conviene disparar el callback del widget para que la página lo dé por válido:
# Selenium
driver.execute_script("""
const input = document.querySelector('input[name="cf-turnstile-response"]');
if (input) input.value = arguments[0];
// Also set in the Turnstile widget's callback
const widget = document.querySelector('.cf-turnstile');
const callbackName = widget?.getAttribute('data-callback');
if (callbackName && typeof window[callbackName] === 'function') {
window[callbackName](arguments[0]);
}
""", token)
Si el formulario sigue sin aceptar el envío tras inyectar el token, comprueba que el nombre del input es exactamente cf-turnstile-response y que el callback existe: algunas integraciones solo validan el token cuando ese callback se ejecuta, no cuando el campo oculto tiene un valor.
Preguntas frecuentes
¿El sitekey de Turnstile cambia en cada carga de la página?
No. El sitekey es un identificador público y estable por sitio (y por widget): puedes extraerlo una vez y reutilizarlo. Lo que caduca es el token que devuelve la resolución, no el sitekey.
¿Necesito un navegador headless para extraer el sitekey?
Depende. Si el data-sitekey viene en el HTML inicial, una simple petición con requests basta. Solo necesitas Selenium o Puppeteer cuando el widget se monta por JavaScript después de cargar la página.
¿Por qué CaptchaAI rechaza mi token de Turnstile?
Casi siempre porque el pageurl o el sitekey enviados no coinciden con los del sitio real. Ambos valores deben ser exactamente los de la página donde vas a usar el token; revisa también que usas method=turnstile y no userrecaptcha.
¿CaptchaAI resuelve el modo invisible de Turnstile?
Sí. Turnstile tiene modos "gestionado" y "no interactivo" que no muestran un widget visible, pero el sitekey se extrae igual y el flujo de resolución es el mismo.
Comprobaciones rápidas antes de resolver
Antes de lanzar la extracción y la resolución a escala, verifica estos cuatro puntos:
- Tienes el sitekey completo y sin recortar (empieza por
0x4AAA…). - La
pageurles exactamente la URL donde se muestra el widget, con su protocolo y su ruta. - Envías
method=turnstile, nuncauserrecaptcha. - Inyectas el token y disparas el callback antes de que caduque.
Resuelve Cloudflare Turnstile con CaptchaAI
Ya tienes las tres formas de localizar el sitekey y el código para enviarlo a la API. Crea tu cuenta en captchaai.com, consigue tu API key y resuelve tu primer Turnstile en minutos.