Explicaciones Técnicas

Modos de widget de Cloudflare Turnstile: Managed, Non-Interactive e Invisible

Si automatizas un flujo protegido por Cloudflare Turnstile, la pregunta práctica no es cómo resolver el CAPTCHA, sino qué modo de widget tienes delante: Managed (Cloudflare decide), Non-Interactive (solo prueba de trabajo, sin interfaz) o Invisible (se ejecuta en silencio). Cada uno cambia lo que ve el usuario.

La buena noticia: los tres devuelven el mismo token cf-turnstile-response y se resuelven con la misma llamada a la API. La diferencia real está en la detección. Esta guía explica cómo distinguir cada modo en el HTML y resolverlo con CaptchaAI.


¿Qué modo tienes delante? Guía rápida

Inspecciona el HTML y recorre la lista; el primer criterio que se cumpla te da el modo:

  • ¿Aparece a veces una casilla o recuadro del widget? Es Managed: data-size normal o compact, sin atributos de modo.
  • ¿Solo hay un spinner y nunca una casilla? Es Non-Interactive: busca data-appearance="interaction-only".
  • ¿No hay nada en el viewport pero el campo cf-turnstile-response acaba poblándose? Es Invisible: busca data-size="invisible" o un contenedor oculto.

Modo Managed (el predeterminado)

El modo Managed deja que Cloudflare decida el challenge según la reputación del visitante:

Reputación El widget se representa como
Alta confianza Pase invisible (sin interfaz visible)
Confianza media Casilla de verificación (haz clic para verificar)
Baja confianza Desafío o bloque interactivo

Es el modo más frecuente y el más variable. Piensa en un equipo de QA de una agencia en Madrid o Bogotá que prueba su flujo de login: el mismo formulario puede mostrar la casilla en pruebas manuales y pasar en silencio cuando el tráfico parece confiable. Conviene volver a detectar el modo en cada ejecución, no asumirlo.

Implementación

<!-- Managed mode (default) -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-theme="light">
</div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Cómo detectarlo en el HTML

La señal es una ausencia: hay un cf-turnstile, pero ningún atributo de modo explícito.

  • Está la clase o el div cf-turnstile.
  • No hay data-appearance="interaction-only" ni "always".
  • No hay data-size="invisible".
def is_managed_mode(html):
    """Check if Turnstile is using managed mode (default)."""
    # Managed mode is the default — no explicit mode attribute
    has_turnstile = "cf-turnstile" in html
    has_explicit_mode = 'data-appearance="interaction-only"' in html or \
                        'data-appearance="always"' in html or \
                        'appearance: "interaction-only"' in html
    return has_turnstile and not has_explicit_mode

Modo Non-Interactive

El modo Non-Interactive nunca muestra una casilla ni elemento interactivo. Su comportamiento en tres puntos:

  • Ejecuta un challenge de prueba de trabajo en segundo plano.
  • Solo enseña un spinner mientras trabaja.
  • Si no puede completarse sin interacción, falla en vez de escalar a una casilla.

Trátalo como todo-o-nada: sin casilla de reserva, un fallo es definitivo.

Dónde aparece este modo

  • Formularios de comentarios y widgets de feedback
  • Suscripciones a newsletters
  • Acciones de bajo valor con fricción mínima
  • Endpoints de API con protección del lado del navegador

Implementación

<!-- Non-interactive mode -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-appearance="interaction-only">
</div>

O mediante la API de JavaScript:

turnstile.render('#turnstile-container', {
    sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
    appearance: 'interaction-only',
    callback: function(token) {
        document.getElementById('cf-turnstile-response').value = token;
    },
});

Comportamiento

Page loads → Widget initializes
    ↓
Background proof-of-work runs
    ↓
Success → Token generated (no visible UI)
    OR
Failure → Widget reports error (no fallback to checkbox)

Modo Invisible

El modo Invisible no muestra ningún contenedor en el viewport. Sus rasgos definitorios:

  • No hay recuadro ni spinner: nada visible ni pulsable.
  • Se ejecuta al cargar la página o al activarse por código.
  • Produce un token sin ninguna señal visual.

Implementación

<!-- Invisible mode — container is hidden -->
<div id="turnstile-invisible"
     class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-size="invisible">
</div>

O completamente a través de JavaScript:

// Programmatic invisible Turnstile
turnstile.render('#hidden-container', {
    sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
    size: 'invisible',
    callback: function(token) {
        // Token ready — submit form automatically
        submitForm(token);
    },
    'error-callback': function() {
        // Challenge failed
        console.error('Invisible Turnstile failed');
    },
});

Por qué cuesta más detectarlo

El modo Invisible es el más difícil de localizar por dos razones combinadas:

  • El contenedor no tiene dimensiones visibles: una inspección superficial del DOM no lo delata.
  • A menudo se inyecta por JavaScript tras el render inicial, cuando el HTML estático ya ni lo menciona.

Por eso la detección combina varias señales con distintos niveles de confianza:

import re

def detect_invisible_turnstile(html):
    """Detect invisible Turnstile on a page."""
    indicators = {
        "script_loaded": "challenges.cloudflare.com/turnstile" in html,
        "size_invisible": 'data-size="invisible"' in html or
                          "size: 'invisible'" in html or
                          'size: "invisible"' in html,
        "api_render_call": "turnstile.render" in html,
        "response_field": "cf-turnstile-response" in html,
    }

    if indicators["script_loaded"] and indicators["size_invisible"]:
        return {"mode": "invisible", "confidence": "high"}
    elif indicators["script_loaded"] and indicators["api_render_call"]:
        return {"mode": "invisible_or_programmatic", "confidence": "medium"}
    elif indicators["response_field"]:
        return {"mode": "turnstile_present", "confidence": "low"}

    return {"mode": "none", "confidence": "high"}

Cómo extraer el sitekey en cualquier modo

El sitekey es el único dato que necesitas. Esta función lo extrae en los tres modos:

import re

def extract_turnstile_sitekey(html):
    """Extract Turnstile sitekey from page HTML (works for all modes)."""

    # Pattern 1: data-sitekey attribute in HTML
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', html)
    if match:
        return match.group(1)

    # Pattern 2: JavaScript render call
    match = re.search(r"sitekey:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    # Pattern 3: Turnstile config object
    match = re.search(r"siteKey['\"]?\s*[:=]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    return None

Cómo resolver los tres modos con CaptchaAI

Con CaptchaAI, los tres modos se resuelven igual: el modo no cambia la llamada a la API. El flujo es siempre el mismo:

  1. Extrae el sitekey del HTML y toma la pageurl de la página.
  2. Envía la tarea con el método turnstile.
  3. Sondea el resultado hasta recibir el token cf-turnstile-response.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile(sitekey, page_url):
    """Solve any Turnstile mode — managed, non-interactive, or invisible."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    for _ in range(60):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Turnstile solve timed out")


# Use with any mode
token = solve_turnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
print(f"Token: {token[:50]}...")

Node.js

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveTurnstile(sitekey, pageUrl) {
  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "turnstile",
      sitekey,
      pageurl: pageUrl,
      json: 1,
    },
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId, json: 1 },
    });

    if (result.data.status === 1) {
      return result.data.request;
    }
  }

  throw new Error("Turnstile solve timed out");
}

// Same function works for all Turnstile modes
solveTurnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
  .then((token) => console.log("Token:", token.substring(0, 50)));

Como toda la lógica pasa por la API —no por el navegador—, no necesitas un navegador headless solo para el widget.


Los tres modos, lado a lado

Con cada modo ya claro, esta tabla resume las diferencias que importan:

Característica Managed Non-Interactive Invisible
¿Widget visible? A veces Nunca (solo spinner) Nunca
¿Contenedor requerido? Sí (oculto)
¿Interacción del usuario? A veces (casilla) No No
¿Challenge de prueba de trabajo? Sí (puede intensificarse) Sí (siempre) Sí (siempre)
¿Casilla alternativa? No (falla) No (falla)
Salida de token cf-turnstile-response cf-turnstile-response cf-turnstile-response
Método CaptchaAI turnstile turnstile turnstile
Recomendado para Login, registro Formularios de baja fricción Verificación en segundo plano

En la práctica solo hay una decisión de código: detectar el modo. Resolver con CaptchaAI es idéntico en los tres.


Preguntas frecuentes

¿Qué parámetros necesito para resolver Turnstile, sea cual sea el modo?

Solo dos, idénticos en los tres modos (el método siempre es turnstile):

  • El sitekey del widget.
  • La pageurl donde se renderiza.

Ni el token ni la validación cambian entre modos.

¿El modo Invisible tarda más en resolverse?

No de forma apreciable. El challenge a nivel de API es el mismo en los tres modos; lo que cambia es la experiencia del usuario, no el trabajo del solver. Lo que sí varía es cuánto tardas en encontrar el sitekey.

¿Compact es un cuarto modo?

  • No. data-size="compact" es solo una variante de tamaño del widget.
  • Salvo que se declare lo contrario, funciona en modo Managed; no lo trates como un modo aparte.

¿Un sitio puede cambiar de modo de forma dinámica?

  • Sí. Algunos sitios usan Managed por defecto y cambian a Non-Interactive para páginas o segmentos concretos.
  • El sitekey suele mantenerse, pero conviene volver a detectar el modo en cada navegación.

¿Necesito un navegador headless para resolver Turnstile con CaptchaAI?

No. La resolución ocurre por API: envías el sitekey y la URL, sondeas y recibes el token. No hace falta automatizar un navegador solo para el widget.


Solución de problemas

Síntoma Causa Solución
Token válido pero el formulario lo rechaza Sitekey incorrecto (distinto del widget visible) Busca un sitekey renderizado en JavaScript
Widget no encontrado en el HTML Invisible cargado tras el render inicial Espera a la carga completa y revisa las respuestas XHR
Varios widgets Turnstile en la página Distintos sitekeys por formulario Empareja el sitekey con el formulario concreto
data-size="compact" confunde la detección Compact es una variante de tamaño, no un modo Compact usa Managed por defecto
Atributo data-action presente Etiqueta para analítica, no es un modo Incluye la acción si se valida en el servidor
El token caduca antes del envío Los tokens de Turnstile caducan a los 300 s Resuelve justo antes de enviar

Resumen

Los tres modos de Cloudflare Turnstile —Managed, Non-Interactive e Invisible— cambian la experiencia del usuario, pero producen el mismo token cf-turnstile-response y se resuelven igual con el solver de Turnstile de CaptchaAI, con una tasa de éxito alta y estable. Lo que de verdad importa al desarrollador es la detección: Managed deja HTML visible; Invisible exige analizar la página a fondo para localizar el sitekey.

Artículos relacionados

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