API Tutorials

Parâmetros da página de desafio da Cloudflare e fluxo de token

Quando o Cloudflare dispara a página de desafio (HTTP 503, "Just a moment..."), a automação só segue adiante se um cookie confirmar que o desafio foi resolvido — o que depende de parâmetros como ray, md, cType e cTTimeMs. Reproduzir esse fluxo em staging evita a surpresa clássica de automação que funciona em dev e trava em produção.

Resumo rápido: a página devolve HTTP 503 com um ray ID, roda um proof-of-work em JavaScript e libera o acesso com o cookie cookie_qa_validacao — válido de 15 min a 24 h, preso ao IP e ao User-Agent.


Estrutura da página de desafio (HTTP 503)

Toda página de desafio devolve HTTP 503 com os mesmos elementos: status visível, formulário oculto com os parâmetros e o script que orquestra a verificação:

<!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 do desafio: o que cada campo significa

A Cloudflare expõe esses campos em três lugares: no HTML da página, na URL do script de orquestração e no objeto JavaScript carregado em seguida.

Parâmetros visíveis na página HTML

Identificação:

Parâmetro Nome Para que serve
ray Ray ID da Cloudflare Identifica a requisição e vincula o desafio a ela
md Metadados do desafio Estado do desafio, criptografado
r Token de resposta Resposta computada, preenchida pelo JavaScript
chl_opt Opções do desafio Configuração do script de desafio

Rastreamento:

Parâmetro Nome Para que serve
cRay Ray ID secundário Rastreia o desafio internamente
cZone Zona do desafio ID da zona Cloudflare
cUPMDTk Carimbo de tempo Momento de emissão do desafio
cHash Hash do desafio Valida a integridade dos dados

Parâmetros na URL do script de orquestração

O script é carregado a partir de uma URL com estrutura fixa:

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123

Decompondo essa URL:

  • /cdn-cgi/challenge-platform/ — infraestrutura de desafio
  • h/g/ — versão/variante
  • orchestrate/ — endpoint de orquestração
  • chl_page/v1 — versão da página
  • ray=ABC123 — vincula a requisição ao Ray ID

Parâmetros carregados pelo JavaScript do desafio

// 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',          // sinal de navegador version
    cTTimeMs: '4000',    // Minimum wait time (ms)
    cTplV: 5,            // Template version
    cLt: '...',          // Challenge lifetime
    cRq: {},             // Challenge request data
};

Como o token é gerado: do desafio à liberação de acesso

O fluxo completo, requisição por requisição

O Cloudflare troca nove mensagens até liberar o acesso — o mesmo fluxo que a CaptchaAI reproduz pela API:


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 sinal de navegador:

      - 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 sinal de navegador + 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?
   - sinal de navegador consistent with real browser?
   - No replay (nonce check)?
   ↓

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

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

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

A sequência de cookies fica assim:

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

Challenge solve:
    → cookie_qa_validacao cookie set

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

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

Cookies do desafio

Quatro cookies aparecem no fluxo; só o de liberação prova que o desafio foi resolvido — os outros cuidam de sessão e roteamento:

Cookie Para que serve Vida útil Escopo
__cf_bm Rastreamento de sessão do bot 30 minutos Domínio
cookie_qa_validacao Prova de que o desafio foi resolvido 15 min – 24 horas (configurável) Domínio
__cflb Afinidade do balanceador de carga Sessão Domínio
_cfuvid ID de visitante único Sessão Domínio

O cookie cookie_qa_validacao só é válido dentro do contexto em que foi emitido:

Fator Regra
Endereço IP Precisa vir do mesmo IP que resolveu o desafio
User-Agent Precisa bater com o UA usado durante o desafio
Domínio Vale apenas para o domínio que emitiu o cookie
# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use cookie_qa_validacao 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

Como extrair os parâmetros do desafio via código

As funções abaixo detectam a página de desafio (503) e extraem os parâmetros para debug.

Python

import re
import requests

def extract_challenge_params(url):
    """Extract Cloudflare Turnstile em staging 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')}")

Node.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);

Resolvendo o desafio com a API da CaptchaAI

Você não reimplementa nada disso: a CaptchaAI processa o desafio — página, JS, prova de trabalho e cookie — e devolve o resultado pela API:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile_staging(target_url):
    """Solve Cloudflare Turnstile em staging page — CaptchaAI handles token flow."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile_staging",
        "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_turnstile_staging("https://protected-site.com/login")

Equipes de QA no Brasil repetem esse fluxo antes de picos previsíveis como a Black Friday, rodando os testes em staging.example.com a partir de workers na região sa-east-1 (São Paulo) da AWS para medir a latência real. Como o ambiente usa dados fictícios, a LGPD não entra em jogo.

Escopo: rode esse fluxo só em domínios próprios ou ambientes autorizados — staging, contas de QA, dados fictícios.


Como depurar e corrigir falhas no desafio Cloudflare

Onde o fluxo costuma quebrar

Ponto de falha Sintoma Causa raiz
A página de desafio não carrega Timeout ou resposta vazia Problema de rede ou proxy
O script não executa Desafio entra em loop APIs de JavaScript ausentes no ambiente
A prova de trabalho falha Spinner infinito Timeout no cálculo
Resposta rejeitada Redirecionamento de volta ao desafio Violação de tempo ou sinal de navegador incompatível

Checklist de depuração passo a passo

A função abaixo reproduz o fluxo em etapas para isolar onde a automação trava:

def debug_challenge_flow(url, cookie_qa_validacao_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 cookie_qa_validacao
    if cookie_qa_validacao_cookie:
        session = requests.Session()
        session.cookies.set("cookie_qa_validacao", cookie_qa_validacao_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": "cookie_qa_validacao rejected",
                "possible_causes": [
                    "Cookie expired",
                    "IP address changed",
                    "User-Agent mismatch",
                    "Cookie from different domain",
                ],
            })

    return steps

Erros frequentes e como corrigir

  • "managed" mas a resolução falha — o site exige Turnstile, não o desafio em JavaScript: use o método turnstile em vez de turnstile_staging.
  • cookie_qa_validacao funciona uma vez e depois é rejeitado — rotação de IP mudou seu endereço: fixe o IP durante a vida útil do cookie.
  • "Just a moment..." nunca resolve — JavaScript bloqueado ou malformado: use a CaptchaAI em vez de resolver manualmente.
  • Desafio reaparece a cada requisição — cookie_qa_validacao não está sendo enviado: garanta que os cookies persistam na sessão.
  • Desafio diferente em caminhos diferentes — regras de WAF por caminho: resolva separadamente para cada caminho.

Perguntas frequentes

Preciso simular o desafio da Cloudflare em staging antes de ir para produção?

Vale a pena em automações longas: reproduzir o fluxo em staging revela problemas de timing e cookie antes da produção.

Depende do site: 30 min por padrão, até 24 h para enterprise. Ao expirar, a próxima requisição recebe o desafio de novo.

Geralmente é IP ou User-Agent incompatível: o cookie só vale para quem resolveu o desafio — rotação de proxy no meio da sessão é a causa mais comum.

É possível reproduzir esse fluxo sem executar JavaScript de verdade?

Não. A prova de trabalho e o sinal de navegador rodam no JavaScript da página — sem executá-lo, não existe token válido para devolver à Cloudflare.

A CaptchaAI resolve o desafio Cloudflare Turnstile automaticamente durante os testes?

Sim. A CaptchaAI processa o fluxo — página, JavaScript e prova de trabalho — e devolve o cookie de liberação pronto para uso.


Resumo

A página de desafio carrega Ray IDs, um script de orquestração e um objeto de opções que alimentam um fluxo de prova de trabalho, do qual nasce o cookie cookie_qa_validacao — preso ao IP e ao User-Agent, válido de 15 min a 24 h. Com a CaptchaAI, você não implementa esse protocolo: o solver cuida do fluxo e ajuda a apontar onde a automação travou.

Artigos relacionados

Os comentários estão desativados para este artigo.