Kalau respons dari CaptchaAI sudah status: 1 dan token reCAPTCHA sudah di tangan, tapi form di browser tetap menolak submit, penyebabnya hampir tidak pernah ada di sisi API — CaptchaAI sudah menyelesaikan tugasnya. Masalahnya ada di enam titik umum saat token itu berpindah dari response JSON ke DOM: selector yang salah, callback yang tidak terpicu, token yang keburu kedaluwarsa, pageurl yang tidak cocok, sampai deteksi bot yang sama sekali tidak berhubungan dengan CAPTCHA. Panduan ini membedah keenamnya satu per satu, lengkap dengan kode Selenium yang bisa langsung Anda adaptasi ke Playwright atau Puppeteer.
Pola Kegagalan yang Paling Sering Terjadi
Sebelum masuk ke perbaikan satu per satu, kenali dulu lima pola yang paling sering muncul di laporan bug automation developer — baik yang mengerjakan proyek freelance scraping/otomatisasi di Upwork atau Fastwork, maupun tim internal yang menjalankan bot price-monitoring:
| Masalah | Gejala | Akar Penyebab |
|---|---|---|
| Token tidak diinjeksikan ke elemen yang benar | Formulir dikirim tanpa token | Selektor textarea salah |
| Token kedaluwarsa sebelum dikirimkan | Situs menampilkan CAPTCHA lagi | Terlalu lambat antara solve dan submit |
| Callback tidak terpicu | Tombol formulir tetap dinonaktifkan | Callback grecaptcha tidak ada |
| Pageurl salah dikirim ke API | Token tidak valid untuk domain ini | Ketidakcocokan URL |
| Deteksi bot di luar CAPTCHA | Diblokir setelah token diterima | sinyal browser |
Detail masing-masing solusi ada di bagian berikut, diurutkan dari yang paling sering ditemui ke yang paling jarang.
Perbaikan 1: suntik token reCAPTCHA v2 dengan benar
Ini penyebab paling umum: token sudah benar, tapi hanya ditulis ke value textarea tanpa memicu callback reCAPTCHA. Google mendesain widget ini supaya form baru menganggap CAPTCHA "selesai" setelah callback jalan — bukan sekadar karena textarea-nya terisi. Fungsi di bawah menangani dua langkah itu sekaligus: mengisi setiap textarea g-recaptcha-response yang ada di halaman (bisa lebih dari satu kalau ada beberapa widget), lalu menelusuri konfigurasi ___grecaptcha_cfg untuk menemukan dan memanggil callback yang sesuai.
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
def inject_recaptcha_token(driver, token):
"""Properly inject reCAPTCHA v2 token in browser."""
# Step 1: Make the response textarea visible and set the value
driver.execute_script("""
// Find all response textareas (may be multiple on page)
var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
textareas.forEach(function(ta) {
ta.style.display = 'block';
ta.value = arguments[0];
});
// Also set via ID if present
var byId = document.getElementById('g-recaptcha-response');
if (byId) {
byId.style.display = 'block';
byId.value = arguments[0];
}
""", token)
# Step 2: Trigger the callback
driver.execute_script("""
// Try standard callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {
var client = clients[key];
// Navigate nested structure to find callback
for (var prop in client) {
var val = client[prop];
if (val && typeof val === 'object') {
for (var subprop in val) {
var subval = val[subprop];
if (subval && typeof subval === 'object' && subval.callback) {
subval.callback(arguments[0]);
return;
}
}
}
}
}
}
// Fallback: try common callback names
if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
""", token)
# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
Perbaikan 2: formulir yang menunggu callback aktif dulu
Sebagian situs sengaja mengunci tombol submit sampai callback reCAPTCHA benar-benar terpicu — bukan sekadar mengecek apakah textarea-nya terisi. Kalau inject_recaptcha_token di atas belum berhasil memicu callback, coba dua pendekatan berikut: ambil nama fungsi dari atribut data-callback pada widget, atau telusuri langsung struktur ___grecaptcha_cfg untuk menemukan callback yang tersembunyi di dalamnya.
def find_and_trigger_callback(driver, token):
"""Find the reCAPTCHA callback and trigger it."""
# Method 1: Extract callback from data attribute
callback_name = driver.execute_script("""
var widget = document.querySelector('.g-recaptcha');
if (widget) {
return widget.getAttribute('data-callback');
}
return null;
""")
if callback_name:
driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
return True
# Method 2: Extract from grecaptcha config
triggered = driver.execute_script("""
try {
var widgetId = 0;
var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
if (typeof callback === 'function') {
callback(arguments[0]);
return true;
}
} catch(e) {}
return false;
""", token)
return triggered
Perbaikan 3: solve dulu, suntik detik itu juga
Waktu adalah musuh terbesar di sini. Token reCAPTCHA v2 punya masa berlaku yang singkat, dan setiap detik yang terbuang antara solve dan submit — screenshot debug, logging berlebihan, jeda sleep() yang kelamaan — menambah risiko token kedaluwarsa sebelum sempat dipakai. Ini kelihatan jelas kalau bot Anda jalan di server yang jauh dari target: automation developer yang deploy dari region seperti AWS ap-southeast-1 (Singapura) atau GCP asia-southeast2 (Jakarta) ke situs yang server-nya di region lain sering menemukan latensi ekstra yang tidak terlihat saat testing lokal. Fungsi berikut memangkas jeda itu: solve, polling, lalu suntikkan token dalam hitungan milidetik setelah status: 1 diterima — bukan menunggu proses lain selesai dulu.
import requests
import time
def solve_and_inject_fast(driver, api_key, sitekey, page_url):
"""Solve CAPTCHA and inject token with minimal delay."""
# Submit solve request
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": 1,
}, timeout=30)
task_id = resp.json()["request"]
# Poll for result
time.sleep(15)
for _ in range(24):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
token = data["request"]
# IMMEDIATELY inject — don't wait
inject_recaptcha_token(driver, token)
# Submit form within 5 seconds
time.sleep(0.5)
return True
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
Perbaikan 4: pageurl yang tepat untuk SPA dan iframe
Untuk SPA dan halaman yang memuat CAPTCHA di dalam iframe, pageurl yang Anda kirim ke CaptchaAI harus sama persis dengan URL yang benar-benar dilihat reCAPTCHA — bukan URL yang Anda pakai saat memanggil driver.get(). Kalau ada redirect setelah navigasi awal, atau widget-nya dimuat di iframe terpisah, kirim driver.current_url saat itu juga, bukan URL yang sudah Anda catat di awal skrip.
def get_correct_pageurl(driver):
"""Get the URL that reCAPTCHA actually sees."""
# Check if CAPTCHA is in an iframe
iframes = driver.find_elements(By.TAG_NAME, "iframe")
for iframe in iframes:
src = iframe.get_attribute("src") or ""
if "recaptcha" in src or "anchor" in src:
# CAPTCHA is in iframe — use parent page URL
return driver.current_url
# For SPAs, use current URL (may differ from initial load)
return driver.current_url
# WRONG — using the URL you navigated to
pageurl = "https://example.com/form" # May have redirected
# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)
Perbaikan 5: deteksi bot yang tidak ada hubungannya dengan CAPTCHA
Token yang valid tidak menjamin form Anda lolos — sebagian situs punya lapisan deteksi bot yang berjalan independen dari CAPTCHA dan baru memblokir setelah submit. Kalau Anda mendapat 403 padahal token diterima, cek dulu sinyal browser otomatis yang mudah dikenali script pihak lain: argumen automation yang masih aktif, ukuran window yang tidak wajar, atau flag otomasi bawaan Selenium yang belum dibersihkan. Terapkan konfigurasi ini hanya pada environment yang memang berwenang Anda otomatisasi — QA internal atau staging milik Anda sendiri, bukan alur login akun orang lain.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
def create_stealth_driver():
"""Create a browser that avoids basic bot detection."""
options = Options()
# Remove automation indicators
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# Set realistic window size
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
# Konfigurasi browser standar
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
return driver
driver = create_stealth_driver()
Perbaikan 6: menyuntikkan token Cloudflare Turnstile
Cloudflare Turnstile tidak pakai textarea tersembunyi seperti reCAPTCHA — tokennya masuk ke input cf-turnstile-response, dan sebagian implementasi lama masih fallback ke nama field g-recaptcha-response. Selain mengisi value-nya, Anda juga perlu men-dispatch event change dan input secara manual, karena banyak widget Turnstile memantau event itu untuk tahu kapan harus mengaktifkan tombol submit — mengisi value lewat JavaScript saja tidak otomatis memicu event tersebut.
def inject_turnstile_token(driver, token):
"""Inject Turnstile token into the page."""
driver.execute_script("""
// Find Turnstile response input
var inputs = document.querySelectorAll(
'input[name="cf-turnstile-response"], ' +
'input[name="g-recaptcha-response"]'
);
inputs.forEach(function(input) {
input.value = arguments[0];
});
// Trigger change event
inputs.forEach(function(input) {
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('input', { bubbles: true }));
});
// Try Turnstile callback
if (window.turnstile) {
var widgets = document.querySelectorAll('[data-callback]');
widgets.forEach(function(w) {
var cb = w.getAttribute('data-callback');
if (typeof window[cb] === 'function') {
window[cb](arguments[0]);
}
});
}
""", token)
Checklist untuk mendiagnosis kegagalan injeksi
Kalau keenam perbaikan di atas belum menyelesaikan masalah, jalankan fungsi ini di browser yang sama untuk melihat persis di mana rantainya putus — apakah textarea-nya ada, apakah reCAPTCHA sudah termuat, atau callback-nya memang tidak ditemukan:
def debug_captcha_injection(driver, token):
"""Print debug info to diagnose injection failures."""
info = driver.execute_script("""
var result = {};
// Check textarea exists
var ta = document.querySelector('[name="g-recaptcha-response"]');
result.textarea_found = !!ta;
result.textarea_value_set = ta ? ta.value.length > 0 : false;
// Check reCAPTCHA loaded
result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';
// Check for callback
var widget = document.querySelector('.g-recaptcha');
result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
result.callback_name = widget ? widget.getAttribute('data-callback') : null;
// Current URL
result.current_url = window.location.href;
// Check for errors in console (basic)
result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');
return result;
""")
for key, value in info.items():
print(f" {key}: {value}")
return info
Ringkasan gejala, penyebab, dan solusi
Butuh jawaban cepat tanpa membaca ulang enam bagian di atas? Cocokkan gejala Anda dengan baris berikut:
| Gejala | Penyebab | Solusi |
|---|---|---|
| Token diset tapi formulir ditolak | Callback tidak terpicu | Temukan dan panggil fungsi callback |
| Tombol kirim tetap dinonaktifkan | Callback yang mengaktifkannya belum dipanggil | Picu callback setelah pengaturan token |
| "CAPTCHA tidak valid" setelah dikirim | Token sudah kedaluwarsa | Kurangi waktu antara solve dan submit |
| 403 setelah pengiriman formulir | Deteksi bot (bukan CAPTCHA) | Gunakan opsi konfigurasi browser standar |
| Berfungsi di request, gagal di browser | URL halaman berbeda | Gunakan driver.current_url sebagai pageurl |
Sebagian besar tiket dukungan bertema "token valid tapi ditolak browser" yang kami terima berujung pada salah satu dari lima baris ini.
Pertanyaan yang sering ditanyakan
Kenapa token yang sukses lewat curl atau Postman malah ditolak Selenium?
Karena API CaptchaAI hanya bertanggung jawab menghasilkan token yang valid — apa yang terjadi setelah itu di DOM sepenuhnya urusan skrip automation Anda. curl dan Postman tidak menjalankan JavaScript halaman, jadi Anda tidak akan pernah melihat masalah callback dari situ. Begitu token masuk ke browser sungguhan, ikuti Perbaikan 1 dan 2 di atas.
Apakah cara ini juga berlaku untuk Playwright dan Puppeteer, bukan cuma Selenium?
Ya. Contoh kode di panduan ini pakai Selenium karena paling umum dipakai automation developer Indonesia, tapi logikanya sama persis di framework lain — ganti driver.execute_script() dengan page.evaluate(), isi textarea atau input yang sama, lalu picu callback yang sama.
Berapa lama sebaiknya saya menunggu sebelum menyuntikkan token?
Sesingkat mungkin. Begitu res.php mengembalikan status: 1, langsung panggil fungsi injeksi — jangan sisipkan screenshot, logging panjang, atau sleep() yang tidak perlu di antaranya. Kalau bot Anda berjalan di region yang jauh dari server target, anggaran waktu ini makin ketat, jadi urutan solve → polling → injeksi harus sependek mungkin seperti pada Perbaikan 3.
Kalau widget-nya tidak punya atribut data-callback, bagaimana cara menemukan callback-nya?
Telusuri ___grecaptcha_cfg.clients lewat console browser, atau pakai fungsi find_and_trigger_callback pada Perbaikan 2 — callback biasanya tersembunyi beberapa level di dalam struktur objek klien. Kalau masih buntu, jalankan Checklist Debug di atas untuk melihat field mana yang kosong.