API Tutorials

Parámetros de la página Cloudflare Challenge y flujo de tokens

Cuando Cloudflare presenta una página de challenge, comienza un flujo de token complejo: desde los parámetros de la página inicial, pasando por la ejecución de JavaScript hasta la cookie qa_validation_cookie final. Entender estos parámetros ayuda a diagnosticar fallos, depurar flujos de automatización y elegir el enfoque de resolución correcto.


Anatomía de la página de desafío

Una página de desafío de Cloudflare (HTTP 503) contiene varios elementos clave:

<!DOCTYPE html>
<html>
<head>
    <title>Just a moment...</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
    <div id="challenge-stage">
        <div id="challenge-body-text">
            Checking if the site connection is secure
        </div>
        <div id="challenge-spinner">
            <!-- Loading spinner -->
        </div>
    </div>

    <div id="challenge-form" style="display:none">
        <form id="challenge-form" action="/..." method="POST">
            <!-- Hidden parameters -->
            <input type="hidden" name="md" value="...">
            <input type="hidden" name="r" value="...">
        </form>
    </div>

    <script src="/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=...">
    </script>
</body>
</html>

Parámetros clave

En la página del desafío

Parámetro Nombre Propósito
ray ID de rayos de Cloudflare Identificador de solicitud único, vincula el desafío con la solicitud original
md Metadatos del desafío Estado de desafío cifrado
r Token de respuesta Respuesta calculada (rellenada por JavaScript)
chl_opt Opciones de desafío Configuración para el script de desafío
cRay rayo de desafío Rayo secundario para seguimiento de desafíos.
cZone Zona de desafío ID de zona de Cloudflare
cUPMDTk Marca de tiempo Hora de emisión del desafío
cHash Hachís de desafío Validación de integridad

En la URL del script de desafío

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123
Componente Significado
/cdn-cgi/challenge-platform/ Infraestructura del desafío Cloudflare
h/g/ Versión de desafío/variant
orchestrate/ Punto final de orquestación de desafío
chl_page/v1 Versión de la página de desafío
ray=ABC123 Solicitar enlace de ID de Ray

En la carga útil de JavaScript

El script de desafío carga parámetros adicionales:

// Extracted from obfuscated challenge script
window._cf_chl_opt = {
    cvId: '2',           // Challenge version
    cType: 'managed',    // Challenge type
    cNounce: '...',      // Cryptographic nonce
    cRay: '...',         // Challenge Ray ID
    cHash: '...',        // Challenge hash
    cUPMDTk: '...',      // Timestamp
    cFPWv: 'g',          // Fingerprint version
    cTTimeMs: '4000',    // Minimum wait time (ms)
    cTplV: 5,            // Template version
    cLt: '...',          // Challenge lifetime
    cRq: {},             // Challenge request data
};

Flujo de tokens desde el desafío hasta la autorización

Flujo paso a paso


1. CLIENT → CLOUDFLARE EDGE
   GET /protected-page
   ↓

2. CLOUDFLARE → CLIENT
   HTTP 503 + Challenge page HTML
   Sets: __cf_bm cookie (bot management tracking)
   Contains: ray ID, challenge script URL
   ↓

3. CLIENT (browser)
   Loads challenge script from /cdn-cgi/challenge-platform/...
   ↓

4. CHALLENGE SCRIPT EXECUTES:
   a. Collects browser fingerprint:

      - Canvas hash
      - WebGL renderer
      - Screen dimensions
      - Installed fonts
      - Timezone
      - Language
   b. Runs proof-of-work:

      - Iterates hash computations
      - Must find answer matching difficulty
   c. Computes timing:

      - Enforces minimum wait (cTTimeMs)
      - Records actual timing
   d. Generates response token:

      - Combines fingerprint + PoW answer + timing
      - Encrypts with challenge nonce
   ↓

5. CLIENT → CLOUDFLARE
   POST /cdn-cgi/challenge-platform/h/g/flow/ov1/...
   Body: { r: "encrypted_response", md: "metadata", ... }
   ↓

6. CLOUDFLARE validates:
   - Proof-of-work answer correct?
   - Timing within acceptable range?
   - Fingerprint consistent with real browser?
   - No replay (nonce check)?
   ↓

7. CLOUDFLARE → CLIENT
   HTTP 200 + Set-Cookie: qa_validation_cookie=...; path=/; expires=...
   + HTTP redirect to original URL
   ↓

8. CLIENT → CLOUDFLARE
   GET /protected-page
   Cookie: qa_validation_cookie=...
   ↓

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

Línea de tiempo de cookies

Request 1: No cookies
    → Challenge page (503)
    → __cf_bm cookie set

Challenge solve:
    → qa_validation_cookie cookie set

Request 2+: qa_validation_cookie + __cf_bm
    → Content served (200)

After ~30 mins: qa_validation_cookie expires
    → Next request triggers new challenge

Galletas de desafío

galleta Propósito Toda la vida Alcance
__cf_bm Seguimiento de sesiones de gestión de bots 30 minutos Dominio
qa_validation_cookie Prueba de autorización de desafío 15 min – 24 h (configurable) Dominio
__cflb Afinidad del balanceador de carga Sesión Dominio
_cfuvid ID de visitante único Sesión Dominio

La cookie qa_validation_cookie tiene como finalidad:

  1. Dirección IP: debe provenir de la misma IP que resolvió el desafío.
  2. Agente de usuario: debe coincidir con la UA utilizada durante el desafío.
  3. Dominio: válido únicamente para el dominio que lo emitió.
# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use qa_validation_cookie from IP B

# ❌ FAILS — UA mismatch
# Solve with Chrome UA, then send requests with Firefox UA

# ✅ WORKS — Same IP + Same UA
session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 ... Chrome/120.0.0.0"
# Use same session for solving and subsequent requests

Extrayendo parámetros de desafío

pitón

import re
import requests

def extract_challenge_params(url):
    """Extract Cloudflare challenge page parameters."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }

    response = requests.get(url, headers=headers, timeout=15, allow_redirects=False)
    html = response.text

    params = {
        "status_code": response.status_code,
        "cf_ray": response.headers.get("cf-ray", ""),
        "is_challenge": response.status_code == 503,
    }

    if not params["is_challenge"]:
        return params

    # Extract Ray ID from page
    ray_match = re.search(r"ray['\"]?\s*[:=]\s*['\"]([a-f0-9]+)['\"]", html, re.I)
    if ray_match:
        params["ray_id"] = ray_match.group(1)

    # Extract challenge script URL
    script_match = re.search(
        r'src=["\'](/cdn-cgi/challenge-platform/[^"\']+)["\']', html
    )
    if script_match:
        params["challenge_script"] = script_match.group(1)

    # Extract challenge options
    opt_match = re.search(r"_cf_chl_opt\s*=\s*\{([^}]+)\}", html)
    if opt_match:
        opt_text = opt_match.group(1)

        # Parse individual options
        for key in ["cType", "cRay", "cHash", "cTTimeMs", "cvId", "cFPWv"]:
            val_match = re.search(
                rf"{key}\s*:\s*['\"]?([^'\"', }}]+)", opt_text
            )
            if val_match:
                params[key] = val_match.group(1)

    # Extract form parameters
    md_match = re.search(r'name=["\']md["\']\s+value=["\']([^"\']+)["\']', html)
    if md_match:
        params["md"] = md_match.group(1)

    # Extract cookies from response
    params["cookies"] = {
        name: value
        for name, value in response.cookies.items()
    }

    return params


# Usage
params = extract_challenge_params("https://protected-site.com")
if params["is_challenge"]:
    print(f"Challenge type: {params.get('cType', 'unknown')}")
    print(f"Ray ID: {params.get('ray_id', params['cf_ray'])}")
    print(f"Min wait: {params.get('cTTimeMs', '?')}ms")
    print(f"Script: {params.get('challenge_script', 'not found')}")

Nodo.js

const axios = require("axios");

async function extractChallengeParams(url) {
  const response = await axios.get(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
      Accept: "text/html,*/*;q=0.8",
    },
    validateStatus: () => true,
    maxRedirects: 0,
  });

  const html = response.data;
  const params = {
    statusCode: response.status,
    cfRay: response.headers["cf-ray"] || "",
    isChallenge: response.status === 503,
  };

  if (!params.isChallenge) return params;

  // Extract challenge script URL
  const scriptMatch = html.match(
    /src=["'](\/cdn-cgi\/challenge-platform\/[^"']+)["']/
  );
  if (scriptMatch) params.challengeScript = scriptMatch[1];

  // Extract challenge type
  const typeMatch = html.match(/cType\s*:\s*['"]?(\w+)/);
  if (typeMatch) params.challengeType = typeMatch[1];

  // Extract timing
  const timeMatch = html.match(/cTTimeMs\s*:\s*['"]?(\d+)/);
  if (timeMatch) params.minWaitMs = parseInt(timeMatch[1]);

  return params;
}

extractChallengeParams("https://protected-site.com").then(console.log);

Resolviendo con CaptchaAI

CaptchaAI maneja todo el flujo de tokens internamente; no es necesario extraer los parámetros del desafío manualmente:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_cloudflare_challenge(target_url):
    """Solve Cloudflare challenge page — CaptchaAI handles token flow."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "cloudflare_challenge",
        "sitekey": "managed",
        "pageurl": target_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("Challenge solve timed out")


# CaptchaAI handles the full flow:
# 1. Loads the challenge page
# 2. Executes JavaScript
# 3. Solves proof-of-work
# 4. Returns clearance token/cookies
token = solve_cloudflare_challenge("https://protected-site.com/login")

Errores de desafío de depuración

Puntos de falla comunes

Punto de falla Síntoma Causa raíz
La página del desafío no se carga Tiempo de espera o respuesta vacía Problema con Network/proxy
El script no se ejecuta Bucles de desafío Faltan API de JavaScript
La prueba de trabajo falla hilandero infinito Tiempo de espera computacional
Respuesta rechazada Redirigir nuevamente al desafío Infracción de tiempo o discrepancia de huellas dactilares
qa_validation_cookie no establecido Falta cookie después de resolver Error de análisis de respuesta
qa_validation_cookie rechazado 403 en solicitud posterior IP o UA no coinciden

Lista de verificación de depuración

def debug_challenge_flow(url, qa_validation_cookie_cookie=None, user_agent=None):
    """Debug the challenge solve flow step by step."""
    ua = user_agent or (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    steps = []

    # Step 1: Initial request
    response = requests.get(
        url,
        headers={"User-Agent": ua, "Accept": "text/html,*/*;q=0.8"},
        timeout=15,
        allow_redirects=False,
    )
    steps.append({
        "step": "initial_request",
        "status": response.status_code,
        "is_challenge": response.status_code == 503,
        "cf_ray": response.headers.get("cf-ray", ""),
    })

    # Step 2: Test with qa_validation_cookie
    if qa_validation_cookie_cookie:
        session = requests.Session()
        session.cookies.set("qa_validation_cookie", qa_validation_cookie_cookie)
        session.headers["User-Agent"] = ua

        response2 = session.get(url, timeout=15, allow_redirects=False)
        steps.append({
            "step": "with_clearance",
            "status": response2.status_code,
            "passed": response2.status_code == 200,
        })

        if response2.status_code != 200:
            steps.append({
                "step": "diagnosis",
                "issue": "qa_validation_cookie rejected",
                "possible_causes": [
                    "Cookie expired",
                    "IP address changed",
                    "User-Agent mismatch",
                    "Cookie from different domain",
                ],
            })

    return steps

Solución de problemas

Síntoma causa Solución
El tipo de desafío está "gestionado" pero la resolución falla El desafío requiere Turnstile, no el desafío JS Pruebe el método turnstile en lugar de cloudflare_challenge
qa_validation_cookie funciona una vez y luego se rechaza La rotación de IP cambió su IP Pin IP durante la vida útil del permiso
"Sólo un momento..." la página nunca se resuelve JavaScript bloqueado o mal formado Utilice CaptchaAI en lugar de resolución manual
El desafío reaparece después de cada solicitud. qa_validation_cookie no se envía Asegúrese de que las cookies persistan en la sesión
Diferentes desafíos en diferentes caminos Reglas WAF por ruta Resuelva para cada camino por separado

Preguntas frecuentes

Es un token cifrado que contiene la prueba de resolución, el hash de IP, el hash de UA y el tiempo de vencimiento. No puedes decodificarlo ni falsificarlo; solo el borde de Cloudflare puede validarlo.

Los operadores del sitio configuran la vida útil. El valor predeterminado es 30 minutos. El rango es de 15 minutos a 24 horas. Los clientes empresariales pueden establecer valores personalizados.

¿Puedo resolver el desafío sin ejecutar JavaScript?

No. El desafío requiere JavaScript para calcular la prueba de trabajo y la señales del navegador del navegador. CaptchaAI maneja esto internamente utilizando navegadores reales.

¿Qué sucede si cambia el ID de Ray?

Cada solicitud obtiene una nueva ID de Ray. El desafío está vinculado al ID de Ray en el momento de la página del desafío. Una vez que se emite qa_validation_cookie, el ID de Ray ya no es relevante.

No. qa_validation_cookie tiene un ámbito de dominio. Cada dominio requiere su propia cookie de resolución y autorización de desafíos.


Resumen

Las páginas de challenge de Cloudflare contienen Ray IDs, scripts de challenge, objetos de opciones y parámetros de formulario que impulsan un flujo de tokens de proof-of-work. El flujo produce una cookie qa_validation_cookie vinculada a IP y User-Agent, válida de 15 minutos a 24 horas. Con CaptchaAI, no es necesario analizar estos parámetros manualmente: el solucionador gestiona todo el flujo. Para la depuración, entender los parámetros ayuda a identificar dónde se interrumpe el flujo.

Artículos relacionados

  • Cloudflare Challenge vs Turnstile: cómo detectar
  • Cloudflare: challenge interactivo vs gestionado
  • Cloudflare Turnstile 403 después del token: solución
Los comentarios están deshabilitados para este artículo.

Publicaciones relacionadas

Comparisons Cloudflare Managed Challenge vs Interactive Challenge
Cloudflare Managed Challenge vs Interactive Challenge: diferencias de comportamiento, códigos HTTP, métodos de resolución con Captcha AI y cuándo usar cada uno.

Cloudflare Managed Challenge vs Interactive Challenge: diferencias de comportamiento, códigos HTTP, métodos de...

Apr 18, 2026
Explainers Reglas WAF de Cloudflare que desencadenan desafíos CAPTCHA
Guía sobre las reglas WAF de Cloudflare que activan desafíos CAPTCHA: qué acciones WAF son solucionables, cómo identificar la regla desencadenante y cómo resolv...

Guía sobre las reglas WAF de Cloudflare que activan desafíos CAPTCHA: qué acciones WAF son solucionables, cómo...

Apr 24, 2026
Explainers Cómo manejar Cloudflare en modo ataque
Cómo manejar el modo 'I'm Under Attack' (IUAM) de Cloudflare: qué es, cómo funciona el challenge Java Script obligatorio de 5 segundos y cómo resolverlo con Cap...

Cómo manejar el modo 'I'm Under Attack' (IUAM) de Cloudflare: qué es, cómo funciona el challenge Java Script o...

Apr 18, 2026