Token Turnstile ditolak padahal CaptchaAI mengembalikannya dengan status sukses? Pada praktiknya penyebabnya hampir selalu satu dari tiga hal: pageurl tidak persis sama dengan halaman tempat widget dimuat, sitekey diambil dari elemen yang keliru, atau token diterapkan lewat jalur yang salah (hidden field vs callback).
Kabar baiknya, setiap kegagalan Turnstile bisa dipetakan ke salah satu dari tiga tahap yang jelas. Begitu Anda tahu tahap mana yang bermasalah, perbaikannya cepat:
- Tahap submit — request ke API ditolak sebelum solving sempat berjalan.
- Tahap hasil (polling) — solving berjalan, tapi polling gagal atau timeout.
- Tahap validasi halaman — API mengembalikan token valid, namun halaman target tetap menolaknya.
CaptchaAI menyelesaikan Turnstile dengan tingkat keberhasilan tinggi dalam waktu kurang dari 10 detik. Jadi kalau integrasi Anda gagal, sumbernya hampir pasti ada di parameter yang Anda kirim atau cara Anda menempelkan token — bukan pada layanan solver-nya. Artikel ini menelusuri ketiga tahap itu satu per satu.
Kenali dulu: Turnstile atau Cloudflare Challenge?
Sebelum men-debug apa pun, pastikan yang Anda hadapi memang Turnstile. Banyak developer habis waktu memperbaiki parameter turnstile padahal halaman sebenarnya menampilkan Cloudflare Challenge full-page — dua produk Cloudflare yang butuh metode API berbeda.
| Sinyal | Turnstile | Cloudflare Challenge |
|---|---|---|
| Yang terlihat | Widget tertanam di halaman (checkbox atau invisible) | Layar verifikasi Cloudflare full-page |
| Yang dikembalikan CaptchaAI | Token untuk dimasukkan ke form | Cookie qa_validation_cookie |
| Metode API | turnstile |
cloudflare_challenge |
| Perlu proxy? | Opsional | Ya (wajib) |
Jika yang muncul adalah Cloudflare Challenge full-page (bukan widget tertanam), Anda butuh Cloudflare Challenge Solver yang mengembalikan cookie qa_validation_cookie dan mewajibkan proxy. Sisa artikel ini fokus pada Turnstile.
Tiga hal yang membuat Turnstile berbeda
Sebelum menyusuri kode error, kenali tiga karakteristik Turnstile yang paling sering jadi akar masalah — dan tidak berlaku sama di jenis CAPTCHA lain:
| Karakteristik | Kenapa jadi sumber error |
|---|---|
| URL halaman harus persis | Token terikat erat ke konteks halaman. Di layar challenge Cloudflare full-page, pageurl yang meleset sedikit — beda path atau ada query parameter yang hilang — sudah cukup membuat token ditolak. |
| Token hanya sekali pakai | Token hanya bisa diverifikasi satu kali. Kalau otomasi mengirimnya dua kali atau ada race condition, percobaan kedua pasti gagal. |
Karakteristik ketiga butuh tabelnya sendiri: token yang dikembalikan bisa ditempel lewat dua jalur, dan memilih yang salah akan gagal tanpa pesan error apa pun.
| Jalur penerapan token | Kapan digunakan |
|---|---|
Hidden field — isikan ke cf-turnstile-response (kadang juga g-recaptcha-response) |
Saat halaman memakai form standar dengan hidden input |
Fungsi callback — panggil fungsi yang didefinisikan di turnstile.render() atau data-callback |
Saat halaman memakai validasi terprogram, bukan form |
Error saat submit task
Error ini muncul saat mengirim task ke https://ocr.captchaai.com/in.php — artinya request Anda ditolak sebelum solving sempat dimulai. Tiga error kunci dan akun berikut paling sering muncul, dan semuanya cukup dicocokkan lewat tabel:
| Kode error | Penyebab | Perbaikan |
|---|---|---|
ERROR_WRONG_USER_KEY |
Format kunci API salah (seharusnya 32 karakter) | Verifikasi kunci dari captchaai.com/api.php |
ERROR_KEY_DOES_NOT_EXIST |
Format kunci benar, tapi belum tertaut ke akun aktif | Buka dashboard, pastikan akun aktif dan kuncinya tepat |
ERROR_ZERO_BALANCE |
Tidak ada thread gratis di paket Anda | Tunggu thread kosong, kurangi concurrency, atau upgrade paket |
| Respons HTML atau 500/502 | Error server sementara | Tunggu 5–10 detik, lalu coba lagi |
Dua error berikutnya perlu sedikit penjelasan tambahan karena menyangkut parameter, bukan sekadar kredensial.
ERROR_PAGEURL
Penyebab: Parameter pageurl tidak ada.
Perbaikan: Tambahkan URL lengkap — protokol, domain, dan path:
pageurl=https://staging.example.com/qa-login
ERROR_BAD_PARAMETERS
Penyebab: Parameter wajib tidak ada atau formatnya salah. Untuk Turnstile, parameter yang wajib adalah:
| Parameter | Tipe | Wajib | Deskripsi |
|---|---|---|---|
key |
String | Ya | Kunci API CaptchaAI Anda |
method |
String | Ya | Harus turnstile |
sitekey |
String | Ya | Sitekey widget Turnstile |
pageurl |
String | Ya | URL halaman lengkap |
Opsional tapi berguna:
| Parameter | Tipe | Deskripsi |
|---|---|---|
action |
String | Nilai data-action atau parameter action dari turnstile.render() |
proxy |
String | Format: login:password@IP:PORT |
proxytype |
String | HTTP, HTTPS, SOCKS4, SOCKS5 |
Perbaikan: Pastikan semua field wajib ada dan diketik dengan benar.
Cara menemukan sitekey Turnstile yang benar
Sitekey adalah parameter yang paling sering salah diambil. Ada tiga tempat untuk menemukannya.
Opsi 1 — atribut data-sitekey:
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAB1example"></div>
Opsi 2 — panggilan turnstile.render():
turnstile.render('#captcha-container', {
sitekey: '0x4AAAAAAAB1example',
callback: function(token) {
document.getElementById('cf-turnstile-response').value = token;
}
});
Opsi 3 — mencegat panggilan render (lanjutan):
Jika sitekey dimuat secara dinamis, Anda bisa mendefinisikan ulang turnstile.render sebelum widget diinisialisasi untuk menangkap parameternya:
// Inject ini sebelum skrip Turnstile dimuat
const originalRender = window.turnstile.render;
window.turnstile.render = function(container, params) {
console.log('Sitekey:', params.sitekey);
console.log('Action:', params.action);
return originalRender.call(this, container, params);
};
Error saat polling hasil
Error ini muncul saat mem-polling https://ocr.captchaai.com/res.php — task sudah diterima, sekarang Anda menunggu hasilnya. Satu status yang terlihat seperti error justru normal: CAPCHA_NOT_READY. Sisanya menandakan ID task atau parameter yang keliru.
CAPCHA_NOT_READY — ini bukan error
Solving masih berjalan. Solve Turnstile di CaptchaAI biasanya butuh kurang dari 10 detik, jadi tunggu 5 detik lalu poll lagi. Baru perlakukan sebagai kegagalan setelah Anda melewati batas jumlah percobaan yang Anda tetapkan sendiri.
ERROR_EMPTY_ACTION
Penyebab: Parameter action tidak ada di request polling Anda.
Perbaikan: Selalu sertakan action=get:
https://ocr.captchaai.com/res.php?key=YOUR_KEY&action=get&id=CAPTCHA_ID&json=1
Catatan: Untuk Turnstile, selalu pakai
json=1di request polling. Respons JSON bisa menyertakanuser_agentsolver, yang dibutuhkan sebagian halaman berproteksi Cloudflare agar validasi token berhasil.
Sisa error polling
Empat error di bawah ini menyangkut ID task atau kondisi server, dan perbaikannya langsung tanpa perlu menyentuh konfigurasi halaman:
| Kode error | Penyebab | Perbaikan |
|---|---|---|
ERROR_WRONG_ID_FORMAT |
ID captcha mengandung karakter non-numerik | Gunakan ID persis seperti yang dikembalikan in.php, tanpa diubah |
ERROR_WRONG_CAPTCHA_ID |
ID tidak cocok dengan task mana pun yang disubmit | Pastikan Anda mem-polling ID yang benar dari respons submit |
ERROR_CAPTCHA_UNSOLVABLE |
Solving gagal — mungkin sitekey salah atau konfigurasi halaman tidak didukung | Verifikasi sitekey, kirim ulang request, lalu coba lagi |
ERROR_INTERNAL_SERVER_ERROR |
Masalah di sisi server | Tunggu 10 detik, lalu coba lagi |
Token valid tapi halaman tetap menolak
Kelompok ini yang paling sulit di-debug: API sudah mengembalikan token, tapi halaman target menolaknya. Artinya masalah ada di sisi penerapan token, bukan di solving.
Kegagalan 1: token masuk ke field yang salah
Gejala: Form terkirim, tapi halaman menampilkan error validasi atau me-refresh.
Halaman Turnstile bisa mengharapkan token di field yang berbeda:
cf-turnstile-response— hidden input Turnstile utamag-recaptcha-response— sebagian halaman memakainya sebagai fallback
Perbaikan: Cek form halaman untuk kedua field. Dalam otomasi browser:
# Selenium — inject into both fields for safety
driver.execute_script("""
var cfField = document.querySelector('[name="cf-turnstile-response"]');
var gField = document.querySelector('[name="g-recaptcha-response"]');
if (cfField) cfField.value = arguments[0];
if (gField) gField.value = arguments[0];
""", token)
Kegagalan 2: callback tidak terpicu
Gejala: Token sudah ada di field, tapi form tetap menolak pengiriman.
Penyebab: Halaman memakai fungsi callback, bukan (atau sebagai tambahan) hidden field. Callback ini menangani logika lanjutan seperti mengaktifkan tombol submit atau mengirim AJAX request.
Perbaikan: Temukan dan panggil callback-nya:
// Cek atribut data-callback
const callbackName = document.querySelector('.cf-turnstile').getAttribute('data-callback');
if (callbackName && window[callbackName]) {
window[callbackName](token);
}
// Atau jika diteruskan di turnstile.render()
// Anda mungkin perlu intercept panggilan render untuk menangkapnya
Dua kegagalan validasi terakhir tidak butuh langkah panjang — cukup kenali gejala dan penyebabnya:
| Kegagalan | Gejala dan penyebab | Perbaikan |
|---|---|---|
| Konteks halaman keliru | Token ditolak meski sitekey benar dan solving baru. pageurl tidak cocok dengan konteks halaman sebenarnya — paling sering pada halaman challenge Cloudflare (query parameter atau path yang penting) dan single-page application (URL address bar berbeda dari URL yang memuat widget). |
Buka tab Network di DevTools, temukan URL persis tempat widget Turnstile dimuat, lalu pakai URL itu sebagai pageurl. |
| Token dipakai ulang | Solving pertama berhasil, solving berikutnya gagal. Token Turnstile hanya sekali pakai; begitu diverifikasi server Cloudflare, token langsung hangus. | Minta solving baru untuk setiap pengiriman form. Jangan cache atau pakai ulang token. |
Tabel referensi cepat: error ke perbaikan
| Error / Gejala | Tahap | Kemungkinan penyebab | Perbaikan |
|---|---|---|---|
ERROR_WRONG_USER_KEY |
Submit | Format kunci API salah | Verifikasi kunci 32 karakter |
ERROR_KEY_DOES_NOT_EXIST |
Submit | Kunci tidak valid | Periksa dashboard |
ERROR_ZERO_BALANCE |
Submit | Tidak ada thread gratis | Tunggu atau upgrade paket |
ERROR_PAGEURL |
Submit | pageurl tidak ada |
Tambahkan URL lengkap |
ERROR_BAD_PARAMETERS |
Submit | Sitekey, method, atau pageurl tidak ada | Verifikasi semua field wajib |
CAPCHA_NOT_READY |
Polling | Solving sedang berjalan | Tunggu 5 detik, coba lagi |
ERROR_WRONG_ID_FORMAT |
Polling | ID captcha non-numerik | Gunakan ID persis dari in.php |
ERROR_WRONG_CAPTCHA_ID |
Polling | ID captcha tidak valid | Verifikasi ID submit |
ERROR_EMPTY_ACTION |
Polling | action=get tidak ada |
Tambahkan parameter action |
| Token ditolak halaman | Validasi | Field salah, callback tak terpicu, URL salah | Cek nama field, panggil callback, verifikasi pageurl |
| Solving kedua gagal | Validasi | Token dipakai ulang | Minta token baru per submit |
Contoh nyata: worker yang token-nya selalu ditolak
Skenario yang umum di tim automation freelance: sebuah worker Python berjalan di AWS ap-southeast-1 (Singapura) untuk QA alur login di staging.example.com. Solving Turnstile sukses — CaptchaAI mengembalikan token dalam hitungan detik — tapi form login tetap menolaknya setiap kali.
Setelah ditelusuri lewat tab Network DevTools, penyebabnya klasik. Aplikasi itu single-page application, dan URL di address bar (staging.example.com/login) berbeda dari URL yang benar-benar memuat widget Turnstile (staging.example.com/auth/challenge). Karena pageurl diisi dengan URL address bar, konteks token tidak cocok dan validasi gagal — persis kasus "konteks halaman keliru" di atas.
Perbaikannya satu baris: ganti pageurl ke URL tempat widget dimuat, bukan URL yang Anda lihat. Menempatkan worker sedekat mungkin dengan target juga membantu — region seperti ap-southeast-3 (Jakarta) memangkas latency sehingga jendela validasi token yang pendek tidak keburu lewat. Dan sesuai UU Pelindungan Data Pribadi (UU 27/2022), pastikan Anda hanya mengotomasi alur milik sendiri atau yang memang Anda punya izin untuk mengujinya.
Contoh lengkap: solve Turnstile dengan Python
import time
import requests
API_KEY = "YOUR_CAPTCHAAI_API_KEY"
SITEKEY = "0x4AAAAAAAB1example"
PAGE_URL = "https://staging.example.com/qa-login"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_turnstile(api_key, sitekey, pageurl):
"""Submit a Turnstile challenge and return the solved token."""
# Submit
submit_resp = requests.post(
SUBMIT_URL,
data={
"key": api_key,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": pageurl,
"json": 1,
},
timeout=30,
)
submit_resp.raise_for_status()
submit_data = submit_resp.json()
if submit_data.get("status") != 1:
raise RuntimeError(f"Submit failed: {submit_data}")
captcha_id = submit_data["request"]
print(f"Task created — captcha ID: {captcha_id}")
# Wait before first poll (Turnstile is fast — 10 seconds is usually enough)
time.sleep(10)
# Poll for result
for _ in range(60):
result_resp = requests.get(
RESULT_URL,
params={
"key": api_key,
"action": "get",
"id": captcha_id,
"json": 1,
},
timeout=30,
)
result_resp.raise_for_status()
result_data = result_resp.json()
if result_data.get("request") == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result_data.get("status") == 1:
return result_data["request"]
raise RuntimeError(f"Polling error: {result_data}")
raise TimeoutError("Turnstile solve timed out")
# Usage
token = solve_turnstile(API_KEY, SITEKEY, PAGE_URL)
print(f"Solved token: {token[:80]}...")
# Inject into cf-turnstile-response and/or g-recaptcha-response
# Then submit the form
Contoh lengkap: solve Turnstile dengan Node.js
const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SITEKEY = "0x4AAAAAAAB1example";
const PAGE_URL = "https://staging.example.com/qa-login";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveTurnstile(apiKey, sitekey, pageurl) {
// Submit
const submitResp = await fetch(SUBMIT_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
key: apiKey,
method: "turnstile",
sitekey: sitekey,
pageurl: pageurl,
json: "1",
}),
});
const submitData = await submitResp.json();
if (submitData.status !== 1) {
throw new Error(`Submit failed: ${JSON.stringify(submitData)}`);
}
const captchaId = submitData.request;
console.log(`Task created — captcha ID: ${captchaId}`);
// Turnstile is fast — wait 10 seconds before first poll
await sleep(10_000);
// Poll for result
for (let i = 0; i < 60; i++) {
const resultResp = await fetch(
`${RESULT_URL}?${new URLSearchParams({
key: apiKey,
action: "get",
id: captchaId,
json: "1",
})}`
);
const resultData = await resultResp.json();
if (resultData.request === "CAPCHA_NOT_READY") {
await sleep(5_000);
continue;
}
if (resultData.status === 1) {
return resultData.request;
}
throw new Error(`Polling error: ${JSON.stringify(resultData)}`);
}
throw new Error("Turnstile solve timed out");
}
// Usage
solveTurnstile(API_KEY, SITEKEY, PAGE_URL)
.then((token) => {
console.log(`Solved token: ${token.slice(0, 80)}...`);
// Inject into cf-turnstile-response and/or g-recaptcha-response
})
.catch(console.error);
Pertanyaan umum
Apakah saya perlu proxy untuk solve Turnstile?
Untuk widget Turnstile standalone, proxy bersifat opsional — cukup kirim sitekey dan pageurl. Proxy baru direkomendasikan pada halaman challenge Cloudflare; tambahkan proxy dan proxytype ke request Anda saat itu.
Kenapa token yang sama gagal saat submit form kedua?
Karena token Turnstile hanya sekali pakai. Begitu diverifikasi server Cloudflare, token langsung hangus. Minta solving baru untuk setiap pengiriman form, dan jangan pernah menyimpan token untuk dipakai ulang.
Bagaimana cara tahu apakah halaman pakai callback atau hidden field?
Periksa elemen .cf-turnstile di DevTools. Kalau ada atribut data-callback, halaman memakai callback. Kalau form punya hidden input cf-turnstile-response, isi field itu. Banyak halaman butuh keduanya — isi hidden field lalu panggil callback-nya.
Apakah parameter action wajib untuk method turnstile?
Tidak wajib saat submit, tapi jika widget didefinisikan dengan data-action atau action di turnstile.render(), kirimkan nilai yang sama agar token cocok dengan konteks. Sebaliknya, di request polling, action=get memang wajib.
Berapa biaya solve Turnstile dengan CaptchaAI?
CaptchaAI menagih per thread, bukan per solve. Paket BASIC ($15/bulan, 5 thread) sudah mencakup solve tak terbatas per thread untuk semua tipe yang didukung, termasuk Turnstile — jadi biaya bulanan Anda tetap berapa pun volume solving-nya.
Perbaiki alur Turnstile Anda
Kalau integrasi Turnstile Anda gagal, jalankan lima langkah ini secara berurutan:
- Verifikasi sitekey — ambil dari
data-sitekeyatauturnstile.render() - Verifikasi pageurl — pakai URL persis, lengkap dengan protokol dan path
- Cek jalur token — halaman memakai
cf-turnstile-response,g-recaptcha-response, atau callback? - Pakai
json=1— selalu minta respons JSON saat polling hasil Turnstile - Jangan pakai ulang token — minta solving baru untuk tiap submit
Mulai dari CaptchaAI Turnstile Solver, cocokkan parameter Anda dengan dokumentasi API, dan baca Cara Kerja Cloudflare Turnstile kalau Anda butuh latar belakang mekanisme widget-nya.