Token Cloudflare Turnstile berlaku sekitar 300 detik terhitung sejak Cloudflare menerbitkannya — bukan sejak API CaptchaAI mengembalikannya ke kode Anda, apalagi sejak Anda submit ke form. Kalau satu saja langkah di alur kerja Anda memakan waktu lebih dari itu (dialog tambahan, halaman review, antrean batch), token yang tadinya valid akan ditolak situs tujuan. Berikut cara mengenali race condition ini dan menutup celahnya dengan pola timing yang benar.
Berapa Lama Masa Berlaku Token Turnstile
Cloudflare membatasi masa berlaku token Turnstile sekitar 300 detik (5 menit) sejak token dibuat. Ini lebih longgar dibanding reCAPTCHA (~120 detik), tapi kelonggaran itu bisa habis cepat kalau alur kerja Anda punya banyak langkah setelah CAPTCHA diselesaikan.
| Jenis CAPTCHA | Masa Berlaku Token |
|---|---|
| reCAPTCHA v2/v3 | ~120 detik |
| Cloudflare Turnstile | ~300 detik |
| hCaptcha | ~120 detik |
Yang sering terlewat: timer mulai berjalan saat Cloudflare menerbitkan token, bukan saat CaptchaAI mengembalikannya ke Anda, dan bukan pula saat kode Anda menerima respons dari res.php.
Kenapa Race Condition Ini Terjadi
Begini pola waktu yang biasanya terjadi:
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
Jam mulai berjalan sejak Time 0:20. Anda punya jendela waktu sampai kira-kira Time 5:20 untuk submit token — kedengarannya longgar. Tapi lihat apa yang terjadi kalau alur kerja Anda punya beberapa langkah setelah token diterima:
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
Tidak ada satu langkah pun yang terasa lambat sendirian — masalahnya adalah akumulasi. Lima langkah tambahan saja sudah cukup menggeser submit ke Time 5:30, 10 detik melewati batas.
Skenario yang Sering Memicu Race Condition
1. Form Multi-Langkah (checkout, pendaftaran, booking)
Form yang mewajibkan beberapa halaman sebelum submit akhir adalah skenario paling umum:
Step 1: Fill personal info → Step 2: Fill address →
Step 3: Solve CAPTCHA → Step 4: Review → Step 5: Submit
Kalau CAPTCHA diselesaikan di Langkah 3 tapi submit baru terjadi di Langkah 5, jeda antara solve dan submit bisa dengan mudah melewati 5 menit — apalagi kalau ada jeda di halaman review.
Ini relevan buat tim automation Indonesia yang menjalankan skrip dari server seperti AWS ap-southeast-1 (Singapura) atau GCP asia-southeast2 (Jakarta) menuju situs travel, ticketing, atau e-commerce multi-langkah. Tiap hop jaringan menambah milidetik, dan pada koneksi mobile-first yang umum dipakai tim scraping freelance, akumulasi delay ini lebih gampang melewati 300 detik dibanding saat testing di jaringan kantor yang stabil.
2. Antrean Pemrosesan Batch
Men-solve token lebih dulu lalu memakainya belakangan adalah pola yang sering bikin batch job gagal diam-diam:
# 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
Token pertama di antrean bisa sudah berumur beberapa menit begitu giliran dipakai tiba — terutama kalau urls berjumlah puluhan dan tiap solve makan waktu 10-15 detik.
3. Retry dengan Token Basi
Memakai ulang token setelah submit pertama gagal adalah bug timing yang paling sering luput:
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
Percobaan kedua dan ketiga di loop ini memakai token yang persis sama dengan percobaan pertama — kalaupun belum kedaluwarsa, situs sering menolaknya karena token itu sudah "dikonsumsi" pada percobaan sebelumnya.
Cara Mencegah Token Kedaluwarsa
Strategi 1: Solve Just-in-Time
Minta token hanya ketika Anda benar-benar siap submit — bukan di awal alur kerja:
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
Pola empat langkahnya tetap sama seperti alur CaptchaAI pada umumnya: kirim task ke in.php → simpan task ID → polling res.php → pakai token — hanya saja langkah terakhir ("pakai token") harus terjadi dalam hitungan detik, bukan menit.
Strategi 2: Lacak Usia Token
Kalau alur kerja Anda tidak bisa dipastikan selesai dalam hitungan detik, bungkus token dengan penanda waktu supaya kode Anda tahu kapan harus solve ulang, alih-alih menebak:
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 menit — safety margin dari batas 5 menit
@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))
# Cek sebelum menggunakan
if timed_token.is_valid:
submit_form(timed_token.token)
else:
# Solve token baru
timed_token = TimedToken(solve_turnstile(site_key, page_url))
submit_form(timed_token.token)
Strategi 3: Token Baru di Setiap Percobaan (JavaScript)
Untuk retry, jangan pernah pakai ulang token lama — solve token baru di setiap percobaan:
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');
}
Mendeteksi dan Mendiagnosis Token yang Kedaluwarsa
Situs jarang menampilkan pesan eksplisit "token kedaluwarsa". Berikut sinyal yang biasanya muncul:
| Sinyal | Kemungkinan Penyebab |
|---|---|
| HTTP 403 setelah submit token | Token tidak valid atau sudah kedaluwarsa |
| Redirect kembali ke halaman form | Validasi token gagal |
| Pesan error umum "verifikasi gagal" | Kegagalan generik — salah satu kemungkinannya kedaluwarsa |
| Halaman challenge muncul lagi | Token ditolak, Cloudflare menantang ulang |
Logging untuk Diagnosis
Catat usia token di setiap submit supaya Anda tidak perlu menebak-nebak saat debugging:
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)")
Tabel Diagnostik Cepat
| Gejala | Penyebab | Solusi |
|---|---|---|
| Token jalan saat testing, gagal di production | Alur kerja production lebih lambat | Solve just-in-time, bukan di awal |
| Submit pertama sukses, retry gagal | Token yang sudah dipakai dipakai ulang | Solve token baru untuk tiap percobaan |
| Kegagalan sesekali di form panjang | Token kedaluwarsa di tengah alur multi-langkah | Pindahkan solve CAPTCHA ke langkah terakhir |
| Batch job punya failure rate tinggi | Token yang di-solve massal basi sebelum dipakai | Solve token sesuai kebutuhan, jangan di muka |
Auto-Refresh di Browser vs Otomasi API-Only
Kalau widget Turnstile berjalan di browser, dia otomatis me-refresh token sebelum kedaluwarsa. Callback expired-callback terpanggil begitu token basi:
turnstile.render('#captcha', {
sitekey: '0x4AAAA...',
callback: (token) => {
console.log('New token:', token);
},
'expired-callback': () => {
console.log('Token expired — widget will auto-refresh');
}
});
Masalahnya, kemudahan ini cuma berlaku untuk alur berbasis browser. Otomasi API-only — skrip Python atau Node.js yang panggil CaptchaAI langsung tanpa merender halaman — tidak mendapat auto-refresh sama sekali. Mengelola kesegaran token sepenuhnya jadi tanggung jawab kode Anda, lewat pola-pola di atas.
Pertanyaan Umum
Apa tanda paling jelas bahwa submit gagal karena token Turnstile kedaluwarsa, bukan sebab lain?
Kombinasi HTTP 403 tepat setelah submit dan halaman challenge yang muncul kembali adalah sinyal paling kuat. Kalau Anda mencatat usia token (lihat bagian logging di atas) dan angkanya sudah di atas 270 detik saat submit terjadi, kemungkinan besar itu penyebabnya.
Apakah CaptchaAI bisa mengirim token baru otomatis sebelum kedaluwarsa, seperti auto-refresh di browser?
Tidak untuk otomasi API-only. Auto-refresh adalah perilaku widget Turnstile di browser, bukan fitur API CaptchaAI. Untuk skrip tanpa browser, Anda tetap perlu solve token baru sendiri begitu masa berlakunya mendekati batas — pola Strategi 1 dan 2 di atas dibuat untuk ini.
Lebih baik pakai widget Turnstile bawaan browser atau solve lewat API CaptchaAI untuk menghindari isu timing ini?
Kalau alur kerja Anda berjalan di browser (Puppeteer atau Playwright yang merender halaman asli), widget bawaan sudah menangani auto-refresh sendiri. Solve lewat API CaptchaAI dipakai justru saat Anda tidak menjalankan browser sama sekali — HTTP client murni, skrip headless tanpa render halaman — dan di situasi itu Anda bertanggung jawab menjaga kesegaran token sendiri.
Apakah race condition seperti ini juga terjadi pada reCAPTCHA?
Ya, dengan margin yang lebih ketat. Token reCAPTCHA v2/v3 hanya berlaku sekitar 120 detik — separuh dari masa berlaku Turnstile — sehingga alur kerja yang masih aman untuk Turnstile bisa saja sudah kedaluwarsa duluan kalau diterapkan apa adanya ke reCAPTCHA.
Seberapa akurat batas 300 detik, dan kenapa kode di atas memakai margin 270 detik?
Angka 300 detik adalah perkiraan dari Cloudflare dan bisa berubah tergantung konfigurasi situs. Margin 270 detik (4,5 menit) yang dipakai pada TimedToken di atas adalah buffer keamanan praktis — bukan angka resmi dari Cloudflare — supaya solve ulang terjadi sebelum token benar-benar ditolak, bukan sesudahnya.
Artikel Terkait
- Cara membedakan Cloudflare Challenge dan Turnstile
- Token Turnstile Kena 403 Setelah Submit — Cara Memperbaikinya
- Kenapa Token Turnstile Invalid Setelah Solving
Langkah Selanjutnya
Hindari token Turnstile basi di production — ambil API key CaptchaAI dan terapkan pola solve just-in-time dengan tingkat keberhasilan tinggi pada tipe yang didukung.