Um mesmo site pode mostrar o Cloudflare Turnstile de três formas diferentes — às vezes aparece uma caixa de seleção, às vezes só um spinner passa rápido pela tela, às vezes não há nada visível. Isso não é inconsistência do site: é o modo do widget, e cada um exige uma estratégia de detecção própria antes da resolução automática.
- Gerenciado: a Cloudflare decide o nível de desafio por visitante; às vezes aparece um checkbox.
- Não interativo: só prova de trabalho em segundo plano; nunca mostra interface, falha em vez de escalar.
- Invisível: nenhum contêiner visível; roda no carregamento da página ou por gatilho de código.
Para automação, os três produzem o mesmo resultado — um token cf-turnstile-response — resolvido com o mesmo método turnstile na API da CaptchaAI.
Modo gerenciado: o padrão do Cloudflare Turnstile
No modo gerenciado, a própria Cloudflare decide o nível de desafio para cada visitante, com base na reputação do tráfego. No HTML, o widget é declarado assim:
<!-- 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>
O que muda para quem automatiza
O modo gerenciado reage aos sinais do navegador que faz a requisição:
- Alta confiança: o widget passa invisível, sem nenhuma UI visível.
- Confiança média: aparece um checkbox — clique para verificar.
- Baixa confiança: desafio interativo mais complexo, ou bloqueio direto.
Esse é o modo mais comum — e também o mais imprevisível para automação, já que o mesmo site pode ou não mostrar UI dependendo dos sinais do momento.
Como detectar no HTML
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
Dica: registre o valor de
data-appearancemesmo quando ausente — a ausência do atributo já é o sinal de que o site está em modo gerenciado.
Modo não interativo do Turnstile: sem clique, só spinner
O modo não interativo nunca mostra checkbox nem qualquer elemento clicável. Ele roda um desafio de prova de trabalho em segundo plano e exibe apenas um spinner de carregamento. Se o desafio não fechar de forma não interativa, ele falha — não existe escalonamento para checkbox. No HTML:
<!-- Non-interactive mode -->
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
data-appearance="interaction-only">
</div>
Ou via API JavaScript:
turnstile.render('#turnstile-container', {
sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
appearance: 'interaction-only',
callback: function(token) {
document.getElementById('cf-turnstile-response').value = token;
},
});
Como funciona o fluxo
Page loads → Widget initializes
↓
Background proof-of-work runs
↓
Success → Token generated (no visible UI)
OR
Failure → Widget reports error (no fallback to checkbox)
Onde esse modo aparece na prática
Aparece em formulários de comentário, inscrição em newsletter, ações de baixo risco onde qualquer atrito atrapalha a conversão, e endpoints de API com proteção no navegador.
Modo invisível do Turnstile: nenhum elemento aparece na tela
O modo invisível é invisível de verdade — nenhum contêiner ocupa espaço na viewport. O widget carrega junto com a página (ou é disparado via código) e entrega o token sem qualquer sinal visual. Declaração no HTML:
<!-- Invisible mode — container is hidden -->
<div id="turnstile-invisible"
class="cf-turnstile"
data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
data-size="invisible">
</div>
Ou totalmente via 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 que é mais difícil de detectar
O Turnstile invisível é mais difícil de identificar porque o contêiner não tem dimensão nenhuma no HTML renderizado:
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"}
Times de QA no Brasil costumam rodar esse tipo de verificação em staging hospedado na região sa-east-1 (São Paulo) da AWS — isso isola a latência do desafio de prova de trabalho da latência de rede até o servidor de origem.
Dica: como o modo invisível costuma servir para verificação em segundo plano, trate tokens e metadados do desafio como qualquer outro dado de sessão — revise as obrigações da LGPD antes de registrar isso em log.
Como extrair a sitekey em qualquer modo do Turnstile
Não importa o modo — a sitekey é sempre necessária para enviar a tarefa à CaptchaAI. Esta função tenta os três padrões mais comuns de onde ela aparece:
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
Resolvendo os três modos do Turnstile com a mesma chamada de API
Gerenciado, não interativo ou invisível — a CaptchaAI resolve os três exatamente da mesma forma. O modo do widget não muda nada na chamada à API:
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)));
Atenção: o loop de 60 tentativas cobre até 5 minutos — bem mais do que o Turnstile costuma levar. Ajuste esse limite conforme o SLA do seu pipeline.
Problemas comuns no Cloudflare Turnstile e como resolver
- Token válido, mas o formulário rejeita: a sitekey usada é diferente da que o widget realmente renderizou — confira o valor gerado via JavaScript, não só o HTML estático.
- Widget não aparece no HTML: o modo invisível carregou depois da renderização inicial — espere o carregamento completo da página e confira as respostas XHR.
- Vários widgets de Turnstile na mesma página: sitekeys diferentes para formulários diferentes — associe cada sitekey ao formulário certo.
data-size="compact"confunde a detecção: compact é uma variante de tamanho, não um modo — por padrão usa o modo gerenciado.- Atributo
data-actionpresente: é uma tag para análise, não indica o modo — inclua a action na resolução só se a validação exigir. - Token expira antes do envio: tokens do Turnstile expiram em 300 segundos — resolva o desafio o mais perto possível do envio.
Perguntas frequentes
O modo do Turnstile muda como a CaptchaAI resolve o desafio?
Não. A CaptchaAI usa o mesmo método turnstile para os três modos — gerenciado, não interativo e invisível. Sitekey e URL da página são os únicos parâmetros exigidos.
Como eu descubro qual modo um site está usando, só olhando o HTML?
Procure pelos atributos data-appearance e data-size. data-size="invisible" indica modo invisível; data-appearance="interaction-only" indica não interativo. Se nenhum aparecer, é o modo gerenciado — o padrão da Cloudflare.
O modo invisível é mais difícil de automatizar do que o gerenciado?
Para detecção, sim — o contêiner não tem dimensão visível, então é preciso inspecionar o script carregado e as chamadas de turnstile.render para achar a sitekey. Para a resolução em si, não: a chamada à API é idêntica nos três modos.
Por quanto tempo um token do Turnstile continua válido depois de gerado?
300 segundos. Se o formulário não for enviado dentro desse intervalo, o token expira e a resolução precisa ser refeita — por isso o ideal é resolver o desafio o mais perto possível do envio.
Cloudflare Turnstile e Cloudflare Challenge são a mesma coisa?
Não. O Turnstile é o widget "sem cliques" que sites de terceiros incorporam na própria página; o Cloudflare Challenge é a página de verificação que a Cloudflare exibe na frente de um site inteiro. A CaptchaAI resolve os dois, mas com métodos de API diferentes — turnstile e cloudflare_challenge.
Em resumo
Os três modos do Cloudflare Turnstile mudam o que o usuário vê, mas entregam o mesmo token cf-turnstile-response, resolvido pelo solucionador de Cloudflare Turnstile da CaptchaAI com o método turnstile e alta taxa de sucesso nos três casos. A diferença para quem desenvolve está na detecção: o gerenciado deixa HTML visível para inspecionar; o invisível exige análise mais profunda da página até achar a sitekey. Consulta rápida:
| Característica | Gerenciado | Não interativo | Invisível |
|---|---|---|---|
| Widget aparece na tela? | Às vezes | Nunca (só o spinner) | Nunca |
| Precisa de elemento contêiner? | Sim | Sim | Sim (oculto) |
| Exige interação do usuário? | Às vezes (checkbox) | Não | Não |
| Roda desafio de prova de trabalho? | Sim (pode escalar) | Sim (sempre) | Sim (sempre) |
| Tem fallback para checkbox interativo? | Sim | Não (falha em vez disso) | Não (falha em vez disso) |
| Onde costuma aparecer | Login, cadastro | Formulários de baixo atrito | Verificação em segundo plano |