Pour résoudre un défi Cloudflare Turnstile avec l'API CaptchaAI, deux valeurs suffisent : le sitekey du widget et l'URL de la page qui l'affiche. Le sitekey n'a rien de secret — il est intégré au HTML côté client pour que le navigateur charge le widget — mais Cloudflare l'expose de plusieurs façons selon la manière dont le site monte Turnstile. Ce guide parcourt les trois emplacements où le sitekey se cache, avec du code prêt à l'emploi pour chacun, puis montre comment envoyer la paire sitekey + URL à CaptchaAI et réinjecter le token obtenu.
Avant tout appel à l'API, vous cherchez donc exactement deux valeurs :
- le sitekey du widget Turnstile (une chaîne qui commence par
0x4AAA...) ; - l'URL exacte de la page qui affiche le défi.
Où se trouve le sitekey Turnstile dans une page
Turnstile expose son sitekey à trois endroits, du plus simple au plus fiable :
- L'attribut
data-sitekeysur les éléments.cf-turnstile - L'appel JavaScript
turnstile.render() - L'URL
srcde l'iframe Turnstile
Commencez toujours par l'attribut DOM : c'est la méthode la moins coûteuse et elle fonctionne dès que la page est rendue côté serveur. Si le widget est monté par JavaScript, l'attribut peut manquer dans le HTML initial ; passez alors à l'analyse de l'appel turnstile.render(). Enfin, le src de l'iframe reste le repli le plus sûr : une fois le widget affiché, le sitekey y figure systématiquement.
Méthode 1 : lire l'attribut data-sitekey dans le DOM
Chaque élément .cf-turnstile porte son sitekey dans l'attribut data-sitekey. Le snippet suivant, à coller dans la console du navigateur, liste tous les widgets de la page ainsi que les attributs data-action et data-cdata — deux paramètres optionnels qu'il faudra transmettre au solveur s'ils sont présents.
// 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 (HTML statique)
Sur une page rendue côté serveur, une simple requête HTTP suffit : le sitekey est déjà dans le HTML renvoyé. Une expression régulière l'extrait sans piloter de navigateur.
import re
import requests
html = requests.get("https://example.com/login").text
matches = re.findall(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)',
html
)
for sk in matches:
print(f"Sitekey: {sk}")
Python (Selenium)
Quand le widget n'apparaît qu'après exécution du JavaScript, chargez la page avec Selenium et lisez l'attribut une fois le DOM construit.
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/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éthode 2 : intercepter l'appel turnstile.render()
Certains sites ne posent aucun attribut data-sitekey dans le HTML : ils appellent turnstile.render() en passant le sitekey en paramètre. Il faut alors récupérer la valeur dans le code JavaScript.
turnstile.render('#captcha-container', {
sitekey: '0x4AAAAAAAB...',
callback: function(token) {
document.getElementById('cf-token').value = token;
},
});
Sur du HTML statique ou un bundle téléchargé, une regex sur l'appel turnstile.render isole la configuration :
# 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)}")
Interception avec Puppeteer
Pour un site en single-page application, interceptez l'appel avant qu'il ne s'exécute. En redéfinissant window.turnstile avec Puppeteer, vous capturez les paramètres exacts passés au widget, sitekey compris :
// 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://example.com/login', { waitUntil: 'networkidle2' });
const params = await page.evaluate(() => window.__turnstileParams);
console.log('Captured Turnstile params:', params);
Méthode 3 : récupérer le sitekey dans le src de l'iframe
Turnstile finit toujours par afficher une iframe pointant vers challenges.cloudflare.com, et le sitekey apparaît dans son src. C'est le repli le plus robuste : même si le sitekey a été injecté dynamiquement, il est présent dans l'URL de l'iframe une fois le widget monté.
Pour choisir rapidement la bonne méthode selon la page que vous ciblez :
- HTML rendu côté serveur → méthode 1, l'attribut
data-sitekey; - sitekey posé par un script ou un bundle → méthode 2, l'appel
turnstile.render(); - widget déjà affiché mais sitekey introuvable dans le source → méthode 3, le
srcde l'iframe.
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]);
}
});
Résoudre Turnstile avec l'API CaptchaAI
Une fois le sitekey et l'URL en main, la résolution suit le schéma classique de l'API CaptchaAI : vous envoyez la tâche à in.php avec method=turnstile, puis vous interrogez res.php jusqu'à récupérer le token. Turnstile se résout généralement en moins de 10 secondes avec un taux de réussite élevé sur les types pris en charge.
Les chiffres de résolution reposent sur des mesures observées et des retours d'utilisateurs. Les résultats varient selon l'environnement, le volume et le moment de la journée.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
SITEKEY = "0x4AAAAAAAB..."
PAGE_URL = "https://example.com/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://example.com/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)}...`);
Côté facturation, CaptchaAI raisonne au thread simultané, sans coût par résolution : le forfait BASIC ($15/mois, 5 threads) couvre déjà un pipeline de surveillance modeste, et vous montez en threads quand la charge augmente. Pour un scraper qui contrôle, par exemple, un tunnel de paiement e-commerce en environnement de staging depuis un worker hébergé chez OVHcloud ou Scaleway, cinq threads absorbent sans peine les pics ponctuels. Si votre collecte touche des données personnelles, limitez-les au strict nécessaire et vérifiez vos obligations RGPD avant de journaliser quoi que ce soit.
Injecter le token cf-turnstile-response dans la page
Le token renvoyé par CaptchaAI n'est utile que s'il atteint le formulaire. Turnstile le stocke dans un champ caché nommé cf-turnstile-response : renseignez cette entrée, puis déclenchez le callback du widget pour que le site considère le défi comme validé.
# 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)
Dépannage
Les échecs d'extraction ou de résolution se ramènent presque toujours à l'un de ces quatre cas :
| Problème | Cause | Correctif |
|---|---|---|
Aucun élément .cf-turnstile trouvé |
Widget rendu dynamiquement | Attendez le chargement complet ou utilisez un MutationObserver |
| Sitekey vide | Défini via l'API JavaScript | Cherchez turnstile.render dans les scripts de la page |
| Token rejeté | Sitekey ou URL de page erronés | Revérifiez que les deux valeurs correspondent au site cible |
Paramètre method incorrect |
userrecaptcha utilisé pour Turnstile |
Passez method=turnstile |
FAQ
Le sitekey Turnstile est-il une donnée confidentielle ?
Non. Le sitekey est une clé publique intégrée au HTML côté client ; l'extraire n'a rien de sensible. La clé secrète, elle, reste sur le serveur du site et ne transite jamais par le navigateur.
En combien de temps CaptchaAI résout-il un défi Turnstile ?
Généralement en moins de 10 secondes, avec un taux de réussite élevé sur les types pris en charge. Un reCAPTCHA v2 peut demander sensiblement plus de temps. Ces durées sont des plafonds indicatifs, pas des moyennes garanties.
Faut-il transmettre data-action et data-cdata au solveur ?
Oui, lorsqu'ils sont présents sur le widget. Certaines intégrations Turnstile lient le token à ces paramètres ; les omettre fait rejeter le token même si le sitekey est correct.
Que faire si le widget Turnstile n'apparaît pas dans le HTML statique ?
Il est monté par JavaScript. Chargez la page avec un navigateur headless (Selenium ou Puppeteer), attendez le rendu, puis lisez l'attribut data-sitekey ou le src de l'iframe.
Résolvez les défis Cloudflare Turnstile avec CaptchaAI
Créez un compte et récupérez votre clé API sur captchaai.com pour envoyer votre première tâche method=turnstile.