Um CAPTCHA que aparece do nada num site protegido pela Cloudflare quase nunca é aleatório: é uma regra do WAF reagindo a um sinal da requisição. Descobrir qual sinal disparou é o primeiro passo para escolher a estratégia certa.
Este guia mostra quais ações produzem um desafio solucionável, como identificar a regra e como resolver cada tipo com a CaptchaAI.
Quatro sinais respondem pela maioria dos casos:
- Pontuação de bot — score baixo em
cf.bot_management.score - País — geolocalização do IP
- Caminho da URL — rotas como
/loginou/api/ - Taxa de requisições — volume acima do limite
O que acontece quando uma regra do WAF dispara
A Cloudflare WAF tem sete ações possíveis, mas só três geram desafio solucionável. Primeiro as que valem a pena resolver:
| Ação do WAF | O que o visitante vê | Código HTTP | Método CaptchaAI |
|---|---|---|---|
| Desafio Gerenciado | Cloudflare decide: invisível, Turnstile ou desafio JS | 503 | turnstile |
| Desafio JS | Página de desafio JavaScript de 5 segundos | 503 | turnstile_staging |
| Desafio Interativo | CAPTCHA tradicional (legado, em desuso) | 403 | turnstile |
E as quatro que não têm desafio para resolver:
| Ação do WAF | O que o visitante vê | Código HTTP |
|---|---|---|
| Bloquear | 403 direto, sem desafio | 403 |
| Permitir | Passa sem verificação | 200 |
| Pular | Ignora as regras restantes | 200 |
| Registrar | Grava o evento, sem ação | 200 |
Desafio Gerenciado: a ação mais comum
É a ação recomendada pela Cloudflare: a decisão do tipo de desafio é adaptativa, visitante por visitante:
WAF rule matches → Managed Challenge triggered
↓
Cloudflare evaluates visitor:
├─ Low risk → Invisible pass (no visible challenge)
├─ Medium risk → Turnstile widget (click to verify)
└─ High risk → JavaScript challenge page
↓
Successful → cookie_qa_validacao cookie issued
Nos três casos, o cookie libera a navegação sem novo desafio por um tempo.
Os padrões de regra que mais pegam tráfego automatizado
Seis padrões, escritos na linguagem de expressão da Cloudflare, respondem pela maior parte dos desafios em automação:
Regras por pontuação de bot
O padrão mais comum: a Cloudflare pontua cada visitante de 0 a 100 e desafia quem fica abaixo do limite:
# Challenge traffic with low bot scores
(cf.bot_management.score lt 30)
→ Action: Managed Challenge
# Challenge non-verified bots
(cf.bot_management.score lt 50 and not cf.bot_management.verified_bot)
→ Action: JS Challenge
Solvers de API da CaptchaAI pontuam como tráfego humano por rodarem em navegadores reais — raramente caem nessa regra.
Regras por país
Bloqueiam ou desafiam pela geolocalização do IP:
# Challenge traffic from specific countries
(ip.geoip.country in {"CN" "RU" "VN" "IN"})
→ Action: Managed Challenge
# Block specific regions entirely
(ip.geoip.country eq "XX")
→ Action: Block
Regras por caminho de URL
Miram rotas específicas — login, cadastro, busca, endpoints de API:
# Challenge login page access
(http.request.uri.path eq "/login" or http.request.uri.path eq "/signup")
→ Action: Managed Challenge
# Challenge API endpoints
(http.request.uri.path contains "/api/")
→ Action: JS Challenge
Regras por taxa de requisições
Entram em ação quando o volume de chamadas de um IP passa do limite:
# Challenge after high request rate
(cf.threat_score gt 10 and http.request.uri.path contains "/search")
→ Action: Managed Challenge
Regras por cabeçalho
Reagem a cabeçalhos ausentes ou a User-Agents que soam como script:
# Challenge requests with no Accept-Language header
(not http.request.headers["accept-language"])
→ Action: JS Challenge
# Challenge requests with suspicious UA
(http.user_agent contains "python" or http.user_agent contains "curl")
→ Action: Managed Challenge
Regras compostas
Combinam várias condições na mesma expressão, tornando o gatilho mais específico:
# Multiple conditions
(cf.bot_management.score lt 30
and http.request.uri.path contains "/api/"
and ip.geoip.country ne "US")
→ Action: JS Challenge
Como descobrir qual regra disparou o desafio
Antes de decidir a estratégia, confirme qual ação apareceu — a própria resposta HTTP já entrega isso, sem acessar o painel do site alvo.
Pelos cabeçalhos e pelo corpo da resposta
Uma função simples já classifica a maioria dos casos pelo status HTTP e por trechos do HTML:
import requests
def check_cloudflare_rule_info(url):
"""Extract WAF rule information from Cloudflare Turnstile em staging response."""
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)
info = {
"status": response.status_code,
"cf_ray": response.headers.get("cf-ray", ""),
"cf_cache_status": response.headers.get("cf-cache-status", ""),
"server": response.headers.get("server", ""),
}
# Challenge-specific info
html = response.text
if response.status_code == 503:
if "jschl" in html:
info["challenge_type"] = "JS Challenge (IUAM or WAF rule)"
elif "challenge-platform" in html:
info["challenge_type"] = "Managed Challenge"
elif "cf-turnstile" in html:
info["challenge_type"] = "Turnstile (Managed Challenge)"
elif response.status_code == 403:
if "cf-ray" in str(response.headers):
info["challenge_type"] = "WAF Block (no challenge)"
else:
info["challenge_type"] = "Origin 403 (not Cloudflare)"
return info
Pelo Ray ID da Cloudflare
Toda resposta da Cloudflare inclui um cabeçalho cf-ray. A regra é privada do operador, mas o Ray ID ajuda a localizar o evento em Segurança > Eventos no painel. Vale citá-lo ao abrir um chamado de suporte.
Como resolver desafios disparados pelo WAF
Depois de identificar a ação, o envio à API muda em só dois parâmetros: método e sitekey.
Estratégia por tipo de desafio
Em Python, a lógica de decisão fica assim:
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_turnstile_staging(url, challenge_type):
"""Solve Cloudflare Turnstile em staging based on WAF rule action."""
if challenge_type == "managed_challenge":
# Managed Challenge typically renders as Turnstile
method = "turnstile"
sitekey = extract_turnstile_sitekey(url)
elif challenge_type == "js_challenge":
# JavaScript Challenge page
method = "turnstile_staging"
sitekey = "managed"
else:
raise ValueError(f"Unknown challenge type: {challenge_type}")
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": method,
"sitekey": sitekey,
"pageurl": 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")
def extract_turnstile_sitekey(url):
"""Fetch page and extract Turnstile sitekey."""
import re
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0",
}
response = requests.get(url, headers=headers, timeout=15)
match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text)
return match.group(1) if match else None
Node.js
A mesma lógica em Node.js, com axios:
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
async function solveWAFChallenge(url, challengeType) {
const method =
challengeType === "js_challenge" ? "turnstile_staging" : "turnstile";
const sitekey =
challengeType === "js_challenge" ? "managed" : await extractSitekey(url);
const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method,
sitekey,
pageurl: url,
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("Challenge solve timed out");
}
async function extractSitekey(url) {
const response = await axios.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
},
});
const match = response.data.match(/data-sitekey=["']([0-9x][A-Za-z0-9_-]+)["']/);
return match ? match[1] : null;
}
O que muda quando o site ajusta o WAF
Regras WAF mudam com frequência, e cada ajuste tem efeito observável na automação:
| Mudança | Efeito na automação | Como identificar |
|---|---|---|
| Regra adicionada | Um caminho liberado passa a exigir desafio | Monitore status 503/403 |
| Regra removida | O desafio some | 200 onde antes vinha 503 |
| Ação endurecida (desafio → bloqueio) | Um desafio solucionável vira bloqueio duro | 403 no lugar de 503 |
| Ação relaxada (bloqueio → desafio) | Bloqueio duro vira desafio solucionável | 503 com página de desafio |
| Limite alterado (pontuação de bot 30 → 50) | Mais requisições são desafiadas | Desafios ficam mais frequentes |
| Escopo de caminho alterado | Outras URLs são afetadas | Caminhos novos retornam desafio |
No Brasil isso aparece na Black Friday: e-commerces apertam a pontuação de bot no pico e relaxam depois — um histórico por hora separa regra de instabilidade.
Como monitorar continuamente
Uma rotina simples registra status e desafio por URL a cada hora, rodando perto do público testado (sa-east-1 para tráfego brasileiro) para não confundir latência com mudança de regra:
import requests
import time
def monitor_cloudflare_protection(urls, interval=3600):
"""Monitor protection changes across URLs."""
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",
}
last_status = {}
while True:
for url in urls:
try:
response = requests.get(
url, headers=headers, timeout=15, allow_redirects=False
)
status = response.status_code
has_challenge = status == 503 or "cf-turnstile" in response.text
current = {"status": status, "challenge": has_challenge}
previous = last_status.get(url)
if previous and current != previous:
print(f"[CHANGE] {url}")
print(f" Before: {previous}")
print(f" After: {current}")
last_status[url] = current
except requests.RequestException as e:
print(f"[ERROR] {url}: {e}")
time.sleep(interval)
Solução de problemas
Cruzando o sintoma com o padrão mais provável, dá para pular direto para a correção:
| Sintoma | Regra provável | Correção |
|---|---|---|
Desafio só em /login |
Regra por caminho | Resolva o desafio para esse caminho |
| Desafio só em IPs de datacenter | Pontuação de bot ou reputação de IP | Faça rotação de proxy ou resolva |
| Desafio muda conforme o país | Regra por país | Use proxy no país permitido ou resolva |
| Desafio aparece após N requisições | Regra por taxa | Reduza a taxa ou resolva cada desafio |
| Desafio é sempre JS (nunca Turnstile) | Ação de Desafio JS (não Gerenciado) | Use o método turnstile_staging |
| 403 sem desafio nenhum | Ação de bloqueio (não solucionável) | Troque IP, cabeçalhos ou padrão de requisição |
Antes de aplicar a correção, confirme três coisas:
- O código HTTP (503 é solucionável, 403 quase sempre não)
- Se o widget Turnstile aparece no HTML ou é página JS pura
- Se o Ray ID muda a cada tentativa (regra ativa, não instabilidade)
Perguntas frequentes
Respostas diretas para as dúvidas que mais aparecem depois que o desafio já foi identificado.
O Desafio Gerenciado e o Cloudflare Turnstile são a mesma coisa?
Não exatamente: o Desafio Gerenciado é a ação do WAF; a Cloudflare decide, por visitante, entre passagem invisível, widget Turnstile ou página JavaScript — o Turnstile é só um dos três resultados.
As regras de WAF valem para o plano gratuito da Cloudflare?
Regras personalizadas ficam nos planos pagos (Pro, Business, Enterprise); o Gratuito tem regras limitadas. O Desafio Gerenciado, porém, vale para todos — por isso é o mais comum na prática.
Depois de resolver o desafio, por quanto tempo a automação continua liberada?
Em geral, por cerca de 30 minutos: o cookie fica vinculado ao IP e ao User-Agent, e mudar qualquer um dos dois invalida a liberação antes do prazo.
Preciso trocar de proxy toda vez que um desafio aparece?
Na maioria dos casos, não — resolver o desafio programaticamente basta. Trocar de proxy só ajuda quando o gatilho é reputação de IP ou geolocalização; do contrário, o desafio se repete.
Resumo
As regras WAF da Cloudflare disparam um CAPTCHA por pontuação de bot, país, caminho, cabeçalhos ou taxa. O Desafio Gerenciado é o mais comum: a Cloudflare decide, por visitante, entre invisível, Turnstile ou página JS. Resolva com a CaptchaAI usando turnstile ou turnstile_staging. Bloqueios diretos (403) não têm desafio — ajuste o padrão de requisição ou o IP.