Quel que soit le mode d'un widget Cloudflare Turnstile, votre automatisation lit toujours le même champ : cf-turnstile-response. Ce qui change, c'est la manière dont le défi apparaît dans la page — et donc la difficulté à le repérer. L'enjeu se résume à détecter le bon sitekey : l'appel de résolution, lui, reste identique dans les trois cas.
En bref, le choix du mode se résume à trois comportements :
- Géré — Cloudflare adapte le niveau de défi à chaque visiteur ; c'est le comportement par défaut.
- Non interactif — une preuve de travail tourne en arrière-plan, sans jamais afficher d'interface.
- Invisible — aucun conteneur n'apparaît à l'écran, l'exécution est totalement silencieuse.
Périmètre : automatisez vos propres environnements (QA, intégration, staging), dans le respect de vos obligations RGPD et des sites que vous êtes autorisé à traiter.
Le mode géré : Cloudflare arbitre le niveau de défi
En mode géré, Cloudflare adapte le niveau de défi à chaque visiteur, du plus discret au plus strict, selon les signaux du navigateur :
- Confiance élevée — pass invisible, aucune interface visible.
- Confiance moyenne — case à cocher (cliquez pour vérifier).
- Faible confiance — défi interactif, voire blocage.
C'est le mode le plus répandu et le plus imprévisible : le widget peut s'afficher ou rester totalement transparent d'une requête à l'autre. Prévoyez les deux cas dans vos tests QA.
Intégration HTML
<!-- 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>
Détecter le mode géré dans le 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
Le mode non interactif : preuve de travail silencieuse
Le mode non interactif n'affiche jamais de case à cocher. Il exécute une preuve de travail en arrière-plan et se contente d'un indicateur de chargement. S'il ne peut aboutir sans interaction, il échoue au lieu d'escalader.
Intégration HTML
<!-- Non-interactive mode -->
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
data-appearance="interaction-only">
</div>
Ou via l'API JavaScript :
turnstile.render('#turnstile-container', {
sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
appearance: 'interaction-only',
callback: function(token) {
document.getElementById('cf-turnstile-response').value = token;
},
});
Déroulé du défi
Page loads → Widget initializes
↓
Background proof-of-work runs
↓
Success → Token generated (no visible UI)
OR
Failure → Widget reports error (no fallback to checkbox)
Quand les sites optent pour le mode non interactif
- Formulaires de commentaires et widgets d'avis
- Inscriptions à une newsletter — fréquent sur les sites média francophones
- Actions à faible enjeu où la friction doit rester minimale
- Endpoints d'API protégés côté navigateur
Le mode invisible : aucun conteneur à l'écran
Le mode invisible mérite son nom : aucun élément conteneur n'apparaît dans la fenêtre. Le widget s'exécute au chargement de la page (ou sur déclenchement programmatique) et produit un token sans le moindre indice visuel.
Intégration HTML
<!-- Invisible mode — container is hidden -->
<div id="turnstile-invisible"
class="cf-turnstile"
data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
data-size="invisible">
</div>
Ou entièrement en 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');
},
});
Pourquoi la détection est plus difficile
Un Turnstile invisible est plus délicat à repérer, car son conteneur n'a aucune dimension visible :
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"}
Extraire le sitekey quel que soit le mode
Quel que soit le mode, le sitekey reste le paramètre indispensable à la résolution. La fonction suivante couvre les trois emplacements où il peut se cacher :
- l'attribut
data-sitekeydirectement dans le HTML ; - l'argument
sitekeypassé à un appelturnstile.render; - une clé
siteKeyau sein d'un objet de configuration JavaScript.
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
Résoudre les trois modes avec l'API CaptchaAI
Les trois modes de Turnstile se résolvent exactement de la même façon avec CaptchaAI : le mode n'a aucune incidence sur l'appel d'API. Le déroulé est toujours le même :
- Envoyez le sitekey et l'URL de la page à la méthode
turnstileviain.php. - Récupérez l'identifiant de tâche renvoyé dans le champ
request. - Interrogez
res.phptoutes les 5 secondes jusqu'au statut prêt. - Lisez le token final et injectez-le dans le champ
cf-turnstile-response.
Un mot sur la capacité : CaptchaAI facture au thread simultané, pas au CAPTCHA résolu.
- BASIC ($15/mois, 5 threads) — intégration et tests QA.
- ADVANCE ($90/mois, 50 threads) — volumes plus soutenus.
- Choisissez le forfait selon le nombre de résolutions que vous menez en parallèle.
En 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://example.com/login")
print(f"Token: {token[:50]}...")
En 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://example.com/login")
.then((token) => console.log("Token:", token.substring(0, 50)));
Identifier le bon mode avant de résoudre
Une rapide inspection de la page évite les mauvaises surprises :
data-appearance="interaction-only"→ mode non interactif.data-size="invisible"→ mode invisible.- Aucun des deux → mode géré (par défaut).
- Dans tous les cas, extrayez le sitekey réellement rendu.
Comparatif récapitulatif des trois modes
Ce tableau résume ce qui distingue les modes côté navigateur et ce qui reste commun côté API :
| Caractéristique | Géré | Non interactif | Invisible |
|---|---|---|---|
| Widget visible ? | Parfois | Jamais (spinner uniquement) | Jamais |
| Élément conteneur requis ? | Oui | Oui | Oui (caché) |
| Interaction utilisateur nécessaire ? | Parfois (case à cocher) | Non | Non |
| Défi de preuve de travail ? | Oui (peut escalader) | Oui (toujours) | Oui (toujours) |
| Repli case à cocher interactive ? | Oui | Non (échoue à la place) | Non (échoue à la place) |
| Champ de token | cf-turnstile-response |
cf-turnstile-response |
cf-turnstile-response |
| Méthode CaptchaAI | turnstile |
turnstile |
turnstile |
| Recommandé pour | Connexion, inscription | Formulaires à faible friction | Vérification en arrière-plan |
Dépannage et cas limites
| Symptôme | Cause probable | Correctif |
|---|---|---|
| Token valide mais le formulaire le refuse | Mauvais sitekey (différent du widget visible) | Cherchez le sitekey rendu en JavaScript |
| Widget introuvable dans le HTML | Mode invisible chargé après le rendu initial | Attendez le chargement complet, inspectez les réponses XHR |
| Plusieurs widgets Turnstile sur la page | Sitekeys distincts selon les formulaires | Associez le bon sitekey au formulaire concerné |
data-size="compact" fausse la détection |
Compact est une variante de taille, pas un mode | Compact reste en mode géré par défaut |
Attribut data-action présent |
Étiquette d'action pour l'analytique, pas un mode | Transmettez l'action à la résolution si la validation l'exige |
| Le token expire avant l'envoi | Les tokens Turnstile expirent au bout de 300 s | Résolvez juste avant la soumission |
Questions fréquentes
Quel champ de token dois-je lire selon le mode ?
Le même dans tous les cas : cf-turnstile-response. Le mode change l'expérience visuelle, jamais le format ni le nom du champ. Une seule logique de récupération couvre donc les trois modes.
Comment détecter un Turnstile invisible chargé après le rendu initial ?
Ne vous fiez pas au seul HTML statique. Repérez-le en trois gestes :
- Attendez le chargement complet de la page avant d'inspecter le DOM.
- Filtrez les requêtes XHR vers
challenges.cloudflare.com/turnstile. - Cherchez
data-size="invisible"ou un appelturnstile.renderinjecté dynamiquement.
data-size="compact" est-il un quatrième mode ?
Non. Compact est uniquement une variante de taille du widget : il reste en mode géré par défaut. Ne le confondez pas avec un mode d'affichage — seuls data-appearance et data-size="invisible" désignent un vrai changement de mode.
Combien de temps un token Turnstile reste-t-il valide ?
Environ 300 secondes. Résolvez le défi juste avant d'envoyer le formulaire : un token généré trop tôt risque d'expirer avant la soumission et de provoquer un rejet côté serveur.
L'essentiel à retenir
Les trois modes de widget de Cloudflare Turnstile — géré, non interactif et invisible — pilotent l'expérience utilisateur mais produisent tous le même token cf-turnstile-response. Côté automatisation, ils se résolvent de façon identique via le solveur Turnstile de CaptchaAI avec un taux de réussite élevé. La vraie différence pour les développeurs se joue à la détection : le mode géré laisse des traces visibles dans le HTML, tandis que le mode invisible impose une analyse plus fine de la page pour retrouver le sitekey.