O Cloudflare Turnstile é uma alternativa a CAPTCHAs focada em privacidade que roda silenciosamente em segundo plano. Diferente dos CAPTCHAs tradicionais, ele raramente exibe um desafio visível: coleta sinais do navegador e emite um token que o backend do site verifica.
Este guia explica como resolver o Turnstile programaticamente usando a API da CaptchaAI. Se você ainda não leu o Quickstart da CaptchaAI, comece por ele — descreve o fluxo geral em 4 passos.
Requisitos
| Item | Valor |
|---|---|
| API key da CaptchaAI | No painel em captchaai.com |
| Sitekey do Turnstile | Extraído da página (começa com 0x) |
| URL da página | URL completa onde o Turnstile aparece |
| Linguagem | Python 3.7+ ou Node.js 14+ |
Passo 1: encontre o sitekey
O sitekey costuma estar no HTML da página, dentro de uma div ou script:
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAC3DHQFLr1GavNl"></div>
Ou renderizado por JavaScript:
turnstile.render('#widget', {
sitekey: '0x4AAAAAAAC3DHQFLr1GavNl',
callback: function(token) { /* ... */ }
});
Três formas de extrair:
- DevTools do navegador — aba Elements, busque
data-sitekeyoucf-turnstile. - Código-fonte —
Ctrl+Ue procure strings que comecem com0x. - Aba Network — filtre por
challenges.cloudflare.com; o sitekey vai nos parâmetros da requisição.
O sitekey do Turnstile sempre começa com
0xe geralmente tem 22 caracteres. Isso o diferencia das chaves de reCAPTCHA, que começam com6L.
Passo 2: envie a tarefa
POST para https://ocr.captchaai.com/in.php com method=turnstile:
import requests
API_KEY = "YOUR_CAPTCHAAI_KEY"
SITEKEY = "0x4AAAAAAAC3DHQFLr1GavNl"
PAGEURL = "https://staging.example.com/qa-login"
r = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "turnstile",
"sitekey": SITEKEY,
"pageurl": PAGEURL,
"json": 1,
})
data = r.json()
if data["status"] != 1:
raise RuntimeError(f"submit failed: {data}")
task_id = data["request"]
print("task id:", task_id)
Equivalente em Node.js:
const axios = require("axios");
const { data } = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: process.env.CAPTCHAAI_KEY,
method: "turnstile",
sitekey: "0x4AAAAAAAC3DHQFLr1GavNl",
pageurl: "https://staging.example.com/qa-login",
json: 1,
},
});
if (data.status !== 1) throw new Error(`submit failed: ${JSON.stringify(data)}`);
const taskId = data.request;
Resposta de sucesso: {"status": 1, "request": "<task_id>"}. Guarde o task_id para o polling.
Passo 3: faça polling do resultado
O Turnstile costuma resolver em 10–25 segundos. Espere 10 segundos e depois faça polling a cada 5 segundos, no máximo 40 iterações:
import time
time.sleep(10)
for _ in range(40):
r = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": 1,
})
res = r.json()
if res["status"] == 1:
token = res["request"]
break
if res["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(f"solver error: {res}")
time.sleep(5)
else:
raise TimeoutError("turnstile solving timed out")
print("token (60 primeiros caracteres):", token[:60])
O token devolvido é uma string Base64 que normalmente começa com 0. e tem 400–600 caracteres.
Passo 4: injete o token na página
Com o token em mãos, coloque-o no campo oculto cf-turnstile-response do formulário e envie.
Selenium:
driver.execute_script(
"document.querySelector('[name=cf-turnstile-response]').value = arguments[0];",
token,
)
driver.find_element("css selector", "form").submit()
Playwright:
page.evaluate(
"(t) => document.querySelector('[name=cf-turnstile-response]').value = t",
token,
)
page.click("button[type=submit]")
HTTP puro: adicione cf-turnstile-response=<token> no corpo application/x-www-form-urlencoded.
O token do Turnstile vale por 120–300 segundos. Use logo após recebê-lo, ou o backend retornará
timeout-or-duplicate.
Exemplo completo em Python
import os, time, requests
API = "https://ocr.captchaai.com"
KEY = os.environ["CAPTCHAAI_KEY"]
def solve_turnstile(sitekey: str, pageurl: str) -> str:
r = requests.post(f"{API}/in.php", data={
"key": KEY, "method": "turnstile",
"sitekey": sitekey, "pageurl": pageurl, "json": 1,
}, timeout=30)
j = r.json()
if j["status"] != 1:
raise RuntimeError(f"submit: {j}")
tid = j["request"]
time.sleep(10)
for _ in range(40):
r = requests.get(f"{API}/res.php", params={
"key": KEY, "action": "get", "id": tid, "json": 1,
}, timeout=30)
j = r.json()
if j["status"] == 1:
return j["request"]
if j["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(f"poll: {j}")
time.sleep(5)
raise TimeoutError("timeout")
if __name__ == "__main__":
print(solve_turnstile("0x4AAAAAAAC3DHQFLr1GavNl", "https://staging.example.com/qa-login"))
Erros comuns
| Código | Significado | Ação |
|---|---|---|
ERROR_WRONG_USER_KEY |
Formato de API key inválido | Verifique se CAPTCHAAI_KEY está completo |
ERROR_KEY_DOES_NOT_EXIST |
API key não encontrada | Copie novamente do painel |
ERROR_ZERO_BALANCE |
Saldo zero | Recarregue e tente de novo |
ERROR_PAGEURL |
Falta o pageurl | Envie a URL completa com https:// |
ERROR_CAPTCHA_UNSOLVABLE |
Falhou após várias tentativas | Verifique se sitekey e pageurl batem; tente uma vez mais |
Mais detalhes no guia de reCAPTCHA v2.
Quando não funciona
- Sitekey dinâmico. Alguns sites Cloudflare emitem novo sitekey a cada visita. Refaça o scraping antes de cada tarefa.
- pageurl exato. O backend do Turnstile compara a URL de forma estrita: envie o caminho exato, sem query string.
- Impressão TLS. A Cloudflare pode bloquear clientes pela assinatura TLS. Use
curl_cffi, Playwright ou um navegador real. - Token expirado. Use em menos de 2 minutos ou terá que resolver de novo.
- Qualidade do proxy. IPs datacenter baratas costumam disparar desafios extras. Prefira proxies residenciais ou móveis.