Explainers

Modes du widget Cloudflare Turnstile : géré, non interactif, invisible

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-sitekey directement dans le HTML ;
  • l'argument sitekey passé à un appel turnstile.render ;
  • une clé siteKey au 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 :

  1. Envoyez le sitekey et l'URL de la page à la méthode turnstile via in.php.
  2. Récupérez l'identifiant de tâche renvoyé dans le champ request.
  3. Interrogez res.php toutes les 5 secondes jusqu'au statut prêt.
  4. 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 appel turnstile.render injecté 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.

Articles connexes

Les commentaires sont désactivés pour cet article.