Troubleshooting

Waktu Kedaluwarsa Token Cloudflare Turnstile dan Race Condition

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

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.

Komentar dinonaktifkan untuk artikel ini.