Tutorials

Como extrair e resolver a sitekey do Cloudflare Turnstile

A sitekey do Cloudflare Turnstile normalmente está em um de três lugares: o atributo data-sitekey de um elemento .cf-turnstile, dentro da chamada JavaScript turnstile.render(), ou embutida na URL do iframe que o widget carrega. Sem ela — e sem a URL da página — a API da CaptchaAI não tem como montar a tarefa, porque o parâmetro sitekey é obrigatório em qualquer chamada com method=turnstile.

Este guia mostra os três métodos de extração na ordem que costuma dar menos trabalho: primeiro a leitura direta do DOM, depois a interceptação da chamada de renderização e, por último, o iframe como último recurso.

Exemplo prático: uma equipe de QA testando o checkout de um e-commerce a partir de São Paulo normalmente resolve o problema já no Método 1 — só recorre à interceptação de JavaScript quando o widget é montado dinamicamente depois do carregamento inicial da página.


Onde a sitekey do Turnstile aparece na página

Onde aparece Quando você encontra
Atributo data-sitekey Widget já renderizado no HTML devolvido pelo servidor (estático ou SSR)
Chamada turnstile.render() Widget montado via JavaScript depois que a página termina de carregar
URL do iframe (src) Sempre presente assim que o widget aparece na tela, mesmo se os dois anteriores falharem

Método 1: ler o atributo data-sitekey no DOM

Esse é o caminho mais barato: nenhuma execução extra de JavaScript é necessária além de percorrer o DOM já carregado. Funciona sempre que o servidor devolve o widget pronto no HTML — o caso mais comum em páginas de login e checkout renderizadas no servidor.

// Browser console
document.querySelectorAll('.cf-turnstile').forEach((el, i) => {
  console.log(`Turnstile ${i}:`, {
    sitekey: el.getAttribute('data-sitekey'),
    action: el.getAttribute('data-action'),
    cData: el.getAttribute('data-cdata'),
    theme: el.getAttribute('data-theme'),
  });
});

Python para HTML estático

Quando você já tem o HTML em mãos — por exemplo, depois de um requests.get() — uma regex simples localiza o atributo sem abrir navegador nenhum:

import re
import requests

html = requests.get("https://staging.example.com/qa-login").text

matches = re.findall(
    r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
    html
)
for sk in matches:
    print(f"Sitekey: {sk}")

Python com Selenium (quando o HTML inicial não basta)

Se o widget só existe depois da hidratação do JavaScript (React, Vue, Next.js), abra a página com Selenium e leia o atributo depois que o elemento aparecer no DOM:

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")

widgets = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
for w in widgets:
    sitekey = w.get_attribute("data-sitekey")
    action = w.get_attribute("data-action")
    print(f"Sitekey: {sitekey}, Action: {action}")

Método 2: capturar a chamada turnstile.render()

Alguns sites não colocam data-sitekey em lugar nenhum do HTML — eles chamam turnstile.render() via JavaScript, passando a sitekey como parâmetro da função. Nesses casos, dois caminhos funcionam: procurar a chamada no bundle por regex, ou interceptar a própria função antes que a página termine de carregar.

turnstile.render('#captcha-container', {
  sitekey: '0x4AAAAAAAB...',
  callback: function(token) {
    document.getElementById('cf-token').value = token;
  },
});

Extrair a sitekey por regex

Se turnstile.render() aparece em texto legível dentro de um <script> (não minificado), a mesma lógica de regex do Método 1 resolve:

# Find turnstile.render calls
render_match = re.search(
    r'turnstile\.render\s*\([^,]*,\s*\{([^}]+)\}',
    html
)
if render_match:
    config = render_match.group(1)
    sk = re.search(r'sitekey\s*:\s*["\']([^"\']+)', config)
    if sk:
        print(f"Sitekey from render: {sk.group(1)}")

Interceptação com Puppeteer (bundle minificado)

Quando o bundle está minificado ou o widget é carregado dinamicamente, é mais confiável interceptar a própria função turnstile.render antes que o site a chame. O trecho abaixo sobrescreve o setter do objeto window.turnstile e registra os parâmetros de cada renderização:

// Intercept turnstile.render before page loads
await page.evaluateOnNewDocument(() => {
  window.__turnstileParams = [];
  const origRender = window.turnstile?.render;
  Object.defineProperty(window, 'turnstile', {
    set(val) {
      this._turnstile = val;
      const orig = val.render;
      val.render = function(container, params) {
        window.__turnstileParams.push(params);
        console.log('Turnstile render:', JSON.stringify(params));
        return orig.apply(this, arguments);
      };
    },
    get() { return this._turnstile; }
  });
});

await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });

const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);

Método 3: extrair a sitekey da URL do iframe

O Cloudflare Turnstile sempre renderiza um iframe apontando para challenges.cloudflare.com assim que o widget aparece na tela, e a sitekey vai embutida na própria URL. É o fallback mais confiável dos três porque não depende do HTML expor o atributo, nem do JavaScript ainda estar sem minificação: se o widget está visível, o iframe existe.

document.querySelectorAll('iframe').forEach(iframe => {
  if (iframe.src.includes('challenges.cloudflare.com')) {
    console.log('Turnstile iframe:', iframe.src);
    const match = iframe.src.match(/sitekey=([A-Za-z0-9_-]+)/);
    if (match) console.log('Sitekey:', match[1]);
  }
});

Qual método tentar primeiro

Na prática, siga essa ordem: teste o Método 1 primeiro, porque é o mais rápido e não exige JavaScript adicional; recorra ao Método 2 se data-sitekey não aparecer no HTML; e use o Método 3 como último recurso sempre que os dois primeiros falharem — o iframe só existe depois que o widget é montado, então ele funciona mesmo contra páginas que carregam o Turnstile de forma assíncrona.


Resolvendo a sitekey do Turnstile com a API da CaptchaAI

Com a sitekey e a URL da página em mãos, o envio para a CaptchaAI segue o mesmo padrão usado para qualquer outro tipo suportado: POST para in.php com method=turnstile, seguido de polling em res.php até o status virar 1. Na prática, o Cloudflare Turnstile costuma resolver em menos de 10 segundos, então um loop de polling com intervalo de 5 segundos como o do exemplo abaixo raramente passa de duas ou três consultas.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://staging.example.com/qa-login"

# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "turnstile",
    "sitekey": SITEKEY,
    "pageurl": PAGE_URL,
    "json": "1",
}).json()

if resp["status"] != 1:
    raise Exception(f"Submit error: {resp['request']}")

task_id = resp["request"]

# Poll
for _ in range(24):
    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["status"] == 1:
        token = result["request"]
        print(f"Turnstile token: {token[:50]}...")
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

const axios = require('axios');

const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: 'YOUR_API_KEY',
    method: 'turnstile',
    sitekey: '0x4AAAAAAAB...',
    pageurl: 'https://staging.example.com/qa-login',
    json: 1,
  }
});
const taskId = submit.data.request;

// Poll for result
let token = null;
for (let i = 0; i < 24; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const poll = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
  });
  if (poll.data.status === 1) {
    token = poll.data.request;
    break;
  }
}
console.log(`Token: ${token.substring(0, 50)}...`);

Enviando o token resolvido de volta ao formulário

O Turnstile guarda o token resolvido em um campo oculto chamado cf-turnstile-response. Depois de receber o token da CaptchaAI, defina o valor desse campo diretamente — e, se o widget expuser um callback (data-callback), dispare-o também, porque alguns formulários só habilitam o botão de envio depois que o callback roda:

# Selenium
driver.execute_script("""
    const input = document.querySelector('input[name="cf-turnstile-response"]');
    if (input) input.value = arguments[0];

    // Also set in the Turnstile widget's callback
    const widget = document.querySelector('.cf-turnstile');
    const callbackName = widget?.getAttribute('data-callback');
    if (callbackName && typeof window[callbackName] === 'function') {
        window[callbackName](arguments[0]);
    }
""", token)

Erros comuns na extração e como corrigir

Nenhum elemento .cf-turnstile no DOM

Causa provável: o widget é montado depois do carregamento inicial da página.

Como corrigir: espere o load da página ou use um MutationObserver.

data-sitekey vazio ou ausente

Causa provável: a sitekey só existe dentro de turnstile.render(), não como atributo HTML.

Como corrigir: aplique o Método 2 — regex no script ou interceptação com Puppeteer.

Token rejeitado pela CaptchaAI ou pelo site de destino

Causa provável: sitekey ou pageurl não correspondem ao domínio real do widget.

Como corrigir: confira os dois valores contra o iframe (src) renderizado na página.

Erro de parâmetro method inválido

Causa provável: envio de userrecaptcha para um Turnstile.

Como corrigir: troque para method=turnstile na chamada a in.php.


Perguntas frequentes

Como encontro a sitekey quando o widget é montado por JavaScript depois do carregamento?

Nesses casos o data-sitekey não aparece no HTML inicial. Use o Método 2: procure a chamada turnstile.render() no bundle por regex ou intercepte a função com Puppeteer antes da página carregar, como mostrado acima.

Qual URL eu envio no parâmetro pageurl?

A URL exata da página onde o widget é exibido. Erros comuns:

  • Usar a URL do iframe (challenges.cloudflare.com).
  • Enviar a URL de um ambiente diferente do que gerou a sitekey.

Se os valores não baterem com o domínio real do widget, o token pode ser rejeitado.

O parâmetro sitekey= na URL do iframe é sempre confiável?

Sim. Assim que o iframe de challenges.cloudflare.com é montado, o valor de sitekey= na sua URL corresponde exatamente ao que você envia no parâmetro sitekey da CaptchaAI — é por isso que o Método 3 funciona como fallback mesmo quando os outros dois falham.

O Cloudflare Turnstile tem modo invisível?

Sim. Os modos "gerenciado" (managed) e "não interativo" (non-interactive) não exibem um widget visível ao usuário. Os três métodos de extração deste guia funcionam da mesma forma nesses modos — a sitekey continua no DOM, na chamada de renderização ou no iframe, mesmo sem o widget aparecer na tela.


Comece a resolver Cloudflare Turnstile agora

Crie sua conta, gere a chave de API e envie sua primeira sitekey em minutos. Obtenha sua chave de API em captchaai.com.


Guias relacionados

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