Un jeton Turnstile qui fonctionne lors des tests échoue en production. La cause : l’expiration du jeton. Les jetons Turnstile ont une durée de vie limitée, et si votre flux de travail prend trop de temps entre la réception du jeton et sa soumission, le site le rejette. Voici comment gérer correctement le timing.
Durée de vie du jeton
Les jetons tourniquet expirent environ 300 secondes (5 minutes) après leur création. C'est plus généreux que les ~120 secondes de reCAPTCHA, mais des conditions de concurrence surviennent toujours dans les flux de travail complexes.
| Type CAPTCHA | Durée de vie du jeton |
|---|---|
| reCAPTCHA v2/v3 | ~120 secondes |
| Cloudflare Turnstile | ~300 secondes |
| hCaptcha | ~120 secondes |
Le minuteur démarre lorsque Cloudflare génère le jeton, pas lorsque CaptchaAI vous le renvoie, ni lorsque vous le recevez dans votre code.
La condition de course
Time 0:00 — You submit a Turnstile task to CaptchaAI
Time 0:15 — CaptchaAI begins solving
Time 0:20 — Token is generated (timer starts here)
Time 0:25 — CaptchaAI returns token to you
Time 0:25+ — Your code processes the token
Time ??? — Your code submits the token to the site
L'horloge tourne à partir de 0h20. Vous avez jusqu'à environ 17 h 20 pour soumettre le jeton. Cela semble généreux, mais considérez ce qui se passe dans un véritable flux de travail :
Time 0:20 — Token generated
Time 0:25 — Received by your code
Time 0:30 — Fill form fields
Time 0:35 — Navigate to next page
Time 1:00 — Handle additional dialogs
Time 2:00 — Wait for page load
Time 4:00 — Network latency spike
Time 5:30 — Submit token → EXPIRED
Scénarios courants de conditions de course
1. Formulaires en plusieurs étapes
Formulaires qui nécessitent plusieurs pages avant la soumission finale :
Step 1: Fill personal info → Step 2: Fill address →
Step 3: Solve CAPTCHA → Step 4: Review → Step 5: Submit
Si CAPTCHA est à l'étape 3 mais que la soumission a lieu à l'étape 5, le délai entre la résolution et la soumission peut dépasser 5 minutes.
2. Files d'attente de traitement par lots
Résoudre les jetons à l'avance et les utiliser plus tard :
# DON'T: Solve all tokens first, then use them
tokens = []
for url in urls:
tokens.append(solve_turnstile(url)) # Tokens age while waiting
for url, token in zip(urls, tokens):
submit_form(url, token) # Early tokens may be expired
3. Réessayez les boucles avec d'anciens jetons
Réutiliser un token après un échec de soumission :
token = solve_turnstile(site_key, page_url)
for attempt in range(3):
result = submit_form(page_url, token)
if result.ok:
break
# BUG: Retrying with the same token — it may be expired OR already consumed
Prévenir l'expiration
Stratégie 1 : Résoudre le problème juste à temps
Demandez le jeton uniquement lorsque vous êtes prêt à le soumettre :
import requests
import time
def solve_turnstile(site_key, page_url):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("Solve timed out")
# Complete all form steps FIRST
fill_personal_info()
fill_address()
navigate_to_review()
# THEN solve and submit immediately
token = solve_turnstile(site_key, page_url)
submit_form(token) # Submit within seconds of receiving the token
Stratégie 2 : suivre l'âge des jetons
import time
class TimedToken:
def __init__(self, token, created_at=None):
self.token = token
self.created_at = created_at or time.time()
self.max_age = 270 # 4.5 min — safety margin from 5 min limit
@property
def is_valid(self):
return (time.time() - self.created_at) < self.max_age
@property
def remaining_seconds(self):
return max(0, self.max_age - (time.time() - self.created_at))
# Usage
timed_token = TimedToken(solve_turnstile(site_key, page_url))
# Check before using
if timed_token.is_valid:
submit_form(timed_token.token)
else:
# Solve a fresh token
timed_token = TimedToken(solve_turnstile(site_key, page_url))
submit_form(timed_token.token)
Stratégie 3 : nouveau jeton lors de la nouvelle tentative (JavaScript)
async function submitWithFreshToken(siteKey, pageUrl, formData) {
const maxRetries = 3;
for (let attempt = 0; attempt < maxRetries; attempt++) {
// Always solve a fresh token for each attempt
const token = await solveTurnstile(siteKey, pageUrl);
const response = await fetch(pageUrl, {
method: 'POST',
body: JSON.stringify({ ...formData, 'cf-turnstile-response': token }),
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) return await response.json();
console.log(`Attempt ${attempt + 1} failed, solving fresh token...`);
}
throw new Error('All attempts failed');
}
Détection des jetons expirés
Le site ne vous indique généralement pas explicitement « jeton expiré ». Recherchez ces signaux :
| Signal | Indications |
|---|---|
| HTTP 403 après la soumission du jeton | Jeton invalide ou expiré |
| Rediriger vers la page du formulaire | La vérification du jeton a échoué |
| Message d'erreur : "Échec de la vérification" | Échec générique – peut être une expiration |
| La page du défi réapparaît | Jeton rejeté, Cloudflare relance les défis |
Journalisation pour le diagnostic
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("turnstile")
token_received_at = time.time()
token = solve_turnstile(site_key, page_url)
logger.info(f"Token received, length: {len(token)}")
# ... workflow steps ...
submit_time = time.time()
age = submit_time - token_received_at
logger.info(f"Submitting token, age: {age:.1f}s")
if age > 270:
logger.warning(f"Token may be expired (age: {age:.1f}s > 270s safety limit)")
Comportement d'actualisation automatique du tourniquet
Dans les flux basés sur un navigateur, les widgets Turnstile actualisent automatiquement les jetons avant leur expiration. Le data-expired-callback se déclenche lorsqu'un jeton expire :
turnstile.render('#captcha', {
sitekey: '0x4AAAA...',
callback: (token) => {
console.log('New token:', token);
},
'expired-callback': () => {
console.log('Token expired — widget will auto-refresh');
}
});
Dans l'automatisation API uniquement (pas de navigateur), vous ne bénéficiez pas de l'actualisation automatique. Vous devez gérer vous-même la fraîcheur des jetons.
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Le jeton fonctionne en test mais pas en production | Le flux de production est plus lent | Résolvez juste à temps, pas à l’avance |
| La première soumission réussit, les nouvelles tentatives échouent | Réutiliser les jetons consommés | Résolvez un nouveau jeton pour chaque tentative |
| Échecs intermittents sur les formulaires longs | Le jeton expire pendant un flux en plusieurs étapes | Déplacer la résolution de CAPTCHA à la dernière étape |
| Les tâches par lots ont un taux d'échec élevé | Les jetons résolus en masse expirent avant utilisation | Résolvez les jetons à la demande, pas par lots |
FAQ
Puis-je prolonger la durée de vie d'un jeton Turnstile ?
Non. L'expiration est définie par Cloudflare et ne peut pas être modifiée. Votre seule option est de résoudre un nouveau jeton.
Quelle est la précision de la limite de 300 secondes ?
C'est approximatif. Cloudflare peut ajuster la synchronisation en fonction de la configuration. Utilisez 270 secondes (4,5 minutes) comme maximum pratique pour une marge de sécurité.
Dois-je pré-résoudre les jetons pour gagner du temps ?
Seulement si votre flux de travail peut les utiliser en quelques minutes. Pour le traitement par lots, résolvez les jetons à la demande plutôt qu’à l’avance.
Articles connexes
- Cloudflare Challenge vs détection de tourniquet
- Comparaison Geetest et Cloudflare Turnstile
- Cloudflare Turnstile 403 après la correction du jeton
Prochaines étapes
Empêcher l'expiration du jeton Turnstile –récupérez votre clé API CaptchaAIet mettez en œuvre une résolution juste à temps avec un taux de réussite de 100 %.