reCAPTCHA Invisible tidak menampilkan checkbox, jadi ketika alur otomatisasi Anda gagal, tidak ada tanda visual yang bisa dijadikan petunjuk. Pada praktiknya hampir semua kegagalan bermuara ke empat titik yang sama:
- Widget invisible tidak terdeteksi di halaman
- Fungsi callback tidak pernah dipanggil setelah token masuk
- Token sudah kedaluwarsa saat akhirnya dikirim
- Request memakai parameter v2 biasa padahal butuh flag
invisible
Halaman ini membedah setiap pola tersebut lengkap dengan perbaikannya — dari deteksi widget sampai wrapper siap produksi. Jika Anda ingin memahami dasar mekanismenya lebih dulu, baca cara kerja reCAPTCHA Invisible dan cara menyelesaikannya.
Peta cepat: kode error dan perbaikannya
Cocokkan pesan error yang Anda terima dengan penyebab dan perbaikannya lewat daftar ini:
ERROR_WRONG_GOOGLEKEY— sitekey keliru atau berasal dari domain lain. Ambil sitekey dari div widget invisible atau panggilangrecaptcha.render().ERROR_PAGEURL— URL tidak cocok; yang dikirim URL halaman induk, bukan URL iframe. Pakai URL persis tempat widget invisible dimuat.ERROR_CAPTCHA_UNSOLVABLE— Google menganggap task tidak mungkin diselesaikan. Coba ulang dengan proxy dan cookie baru; cek apakah situs pindah ke v3.ERROR_BAD_TOKEN_OR_PAGEURL— token ditolak situs target. Pastikan pageurl sama persis; inject lewat callback, bukan hidden field.CAPCHA_NOT_READY— task masih diproses. Lanjutkan polling tiap 5 detik; solve invisible butuh 10–30 detik.ERROR_KEY_DOES_NOT_EXIST— API key CaptchaAI tidak valid. Cek key di captchaai.com/account.- Token diterima tapi form gagal — callback tidak dipanggil setelah token di-inject. Temukan lalu panggil fungsi
data-callbackdengan token tersebut.
Error 1: widget invisible tidak terdeteksi di halaman
reCAPTCHA Invisible tidak punya checkbox yang terlihat. Kalau scraper Anda tidak mengenalinya, request yang dilindungi CAPTCHA akan gagal diam-diam. Dua gejala yang paling khas:
- Form seolah terkirim, tapi server menolak tanpa pesan yang jelas
- Scraper terjebak di halaman yang sama atau dialihkan ke login ulang
Untuk mengenalinya, telusuri salah satu dari tiga pola berikut pada HTML halaman (dan seperti biasa, pastikan Anda hanya memproses data yang memang berhak Anda akses — prinsip yang makin relevan di Indonesia sejak berlakunya UU Pelindungan Data Pribadi / UU 27/2022):
<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
data-size="invisible"
data-callback="onSubmit"></div>
<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
data-sitekey="6LdKlZEU..."
data-callback="onSubmit"
data-action="submit">Submit</button>
<!-- Pattern 3: programmatic render with size: invisible -->
<script>
grecaptcha.render('submit-btn', {
sitekey: '6LdKlZEU...',
callback: onSubmit,
size: 'invisible'
});
</script>
Skrip deteksi otomatis
Python:
import requests
from bs4 import BeautifulSoup
import re
def detect_invisible_recaptcha(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Check for data-size="invisible"
widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
if widget:
return {
"type": "invisible",
"sitekey": widget.get("data-sitekey"),
"callback": widget.get("data-callback")
}
# Check for programmatic render with invisible
scripts = soup.find_all("script")
for script in scripts:
if script.string and "size" in str(script.string) and "invisible" in str(script.string):
key_match = re.search(r"sitekey['"\"']?\s*[:=]\s*['"\"']([^'"\"']+)", script.string)
if key_match:
return {
"type": "invisible-programmatic",
"sitekey": key_match.group(1),
"callback": "check grecaptcha.render() call"
}
return None
Node.js:
const axios = require("axios");
const cheerio = require("cheerio");
async function detectInvisibleRecaptcha(url) {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
// Check for data-size="invisible"
const widget = $(".g-recaptcha[data-size='invisible']");
if (widget.length) {
return {
type: "invisible",
sitekey: widget.attr("data-sitekey"),
callback: widget.attr("data-callback"),
};
}
// Check script tags for programmatic invisible render
const scriptContent = $("script")
.map((_, el) => $(el).html())
.get()
.join("\n");
if (scriptContent.includes("invisible")) {
const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
if (keyMatch) {
return {
type: "invisible-programmatic",
sitekey: keyMatch[1],
callback: "check grecaptcha.render() call",
};
}
}
return null;
}
Error 2: sitekey keliru — ERROR_WRONG_GOOGLEKEY
Error ini muncul ketika sitekey yang Anda kirim tidak cocok dengan widget reCAPTCHA Invisible di halaman target. Tiga penyebab yang paling sering:
- Menyalin sitekey dari checkbox v2 yang ada di halaman lain
- Mengambil sitekey dari anchor URL versi reCAPTCHA yang berbeda
- Halaman memuat beberapa widget reCAPTCHA sekaligus dan Anda mengambil yang keliru
Perbaikan — ambil sitekey invisible yang tepat. Jangan menebak dari halaman lain; ekstrak langsung dari widget yang sedang Anda targetkan:
import requests
from bs4 import BeautifulSoup
def get_invisible_sitekey(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Priority 1: invisible widget
widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
if widget:
return widget["data-sitekey"]
# Priority 2: any g-recaptcha div (may be invisible without data-size)
widget = soup.find(class_="g-recaptcha")
if widget and widget.get("data-sitekey"):
return widget["data-sitekey"]
return None
sitekey = get_invisible_sitekey("https://staging.example.com/qa-login")
print(f"Sitekey: {sitekey}")
Error 3: callback tidak dipanggil — form terkirim tapi tak terjadi apa-apa
Inilah kegagalan reCAPTCHA Invisible paling umum yang terlewat banyak developer. Berbeda dengan checkbox v2 yang cukup mengisi token ke g-recaptcha-response, reCAPTCHA Invisible hampir selalu bergantung pada fungsi callback JavaScript. Kalau Anda meng-inject token tanpa memanggil callback, form tidak akan pernah diproses.
Alur callback-nya singkat:
grecaptcha.execute()memicu invisible challenge- Setelah solve selesai, Google memanggil fungsi yang disebut di
data-callback - Fungsi callback itulah yang mengirim form atau melakukan API call
Perbaikan — temukan lalu jalankan callback. Dua langkah, keduanya wajib.
Langkah 1 — kenali nama callback:
# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property
Langkah 2 — inject token lalu panggil callback (Selenium):
from selenium import webdriver
import requests
import time
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
# Solve with CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": driver.current_url,
"invisible": 1
}).text.split("|")[1]
# Poll for result
token = None
for _ in range(60):
time.sleep(5)
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id
}).text
if resp.startswith("OK|"):
token = resp.split("|")[1]
break
# Inject token into the response field
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')
Langkah 2 — inject token lalu panggil callback (Puppeteer):
const puppeteer = require("puppeteer");
const axios = require("axios");
(async () => {
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://example.com/form");
// Get sitekey and callback
const { sitekey, callback } = await page.evaluate(() => {
const el = document.querySelector(".g-recaptcha[data-size='invisible']");
return {
sitekey: el?.getAttribute("data-sitekey"),
callback: el?.getAttribute("data-callback"),
};
});
// Submit to CaptchaAI
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: "YOUR_API_KEY",
method: "userrecaptcha",
googlekey: sitekey,
pageurl: page.url(),
invisible: 1,
},
});
const taskId = submitResp.data.split("|")[1];
// Poll for result
let token;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: "YOUR_API_KEY", action: "get", id: taskId },
});
if (result.data.startsWith("OK|")) {
token = result.data.split("|")[1];
break;
}
}
// Inject token and fire callback
await page.evaluate(
(tok, cb) => {
document.getElementById("g-recaptcha-response").value = tok;
if (cb && typeof window[cb] === "function") {
window[cb](tok);
}
},
token,
callback,
);
await browser.close();
})();
Error 4: parameter invisible=1 terlupakan
Saat menyelesaikan reCAPTCHA Invisible lewat CaptchaAI, invisible=1 wajib ada di request. Tanpa flag ini, solver memperlakukan task sebagai checkbox v2 biasa — hasilnya bisa ERROR_CAPTCHA_UNSOLVABLE atau token yang ditolak situs target. Sertakan flag ini bila salah satu benar:
- Widget memakai atribut
data-size="invisible" - Solve dipicu lewat
grecaptcha.execute(), bukan klik checkbox
Request yang keliru vs yang benar:
# WRONG — missing invisible=1
params = {
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url
}
# CORRECT — includes invisible=1
params = {
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1 # Required for invisible reCAPTCHA
}
response = requests.get("https://ocr.captchaai.com/in.php", params=params)
Error 5: token kedaluwarsa sebelum sempat dikirim
Token reCAPTCHA Invisible kedaluwarsa dalam 120 detik, sama seperti v2 standar. Masalahnya, alur invisible sering menyisipkan langkah pemrosesan tambahan antara solve dan submit, sehingga token lebih mudah keburu basi.
Gejala yang muncul:
- Form mengembalikan error generik setelah token di-inject
siteverifydi sisi server mengembalikantimeout-or-duplicate- Token sebenarnya valid, tapi terlalu lama sampai ke langkah submit
Perbaikan — solve tepat sebelum submit (just-in-time). Minta solve hanya ketika Anda benar-benar siap mengirim form saat itu juga:
import requests
import time
def solve_invisible_recaptcha(api_key, sitekey, page_url):
# Submit task
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
# Poll for result
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "get",
"id": task_id
})
if result.text.startswith("OK|"):
return result.text.split("|")[1]
if result.text != "CAPCHA_NOT_READY":
raise Exception(f"Solve failed: {result.text}")
raise Exception("Solve timed out after 5 minutes")
# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token
Error 6: token ditolak — ERROR_BAD_TOKEN_OR_PAGEURL
Situs target memverifikasi token ke Google dan mendapat kegagalan. Error ini sering baru muncul justru setelah kode berpindah dari laptop ke server produksi — misalnya scraper yang mulus di lokal tetapi gagal begitu di-deploy ke region seperti AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta), karena IP-nya berubah. Penyebab yang paling sering:
pageurlkeliru — URL tidak cocok dengan domain tempat sitekey didaftarkan. Pakai URL persis tempat widget dimuat.- Token dipakai di domain berbeda — token di-reuse lintas domain. Solve ulang dengan
pageurldomain yang benar. - Token sudah terpakai — token yang sama dikirim dua kali. Minta solve baru untuk setiap submit.
- IP tidak cocok — IP Anda berbeda dari IP solver. Tambahkan parameter
proxyagar sesuai dengan IP sesi. - Flag invisible hilang — di-solve sebagai v2 standar tapi dipakai di halaman invisible. Tambahkan
invisible=1ke request solve.
Checklist debug. Bila kelima titik di atas sudah beres tapi token tetap ditolak, jalankan solve diagnostik ini untuk melihat persis di mana alurnya putus:
def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
"""Run a diagnostic solve with detailed logging."""
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")
print(f"Proxy: {proxy or 'none'}")
params = {
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
}
if proxy:
params["proxy"] = proxy
params["proxytype"] = "HTTP"
# Submit
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
print(f"Submit response: {resp.text}")
if not resp.text.startswith("OK|"):
return None
task_id = resp.text.split("|")[1]
print(f"Task ID: {task_id}")
# Poll with timing
start = time.time()
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get", "id": task_id
})
elapsed = time.time() - start
print(f" [{elapsed:.0f}s] {result.text[:50]}")
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
print(f"Token received after {elapsed:.0f}s")
print(f"Token length: {len(token)} characters")
print(f"Token starts with: {token[:30]}...")
return token
if result.text != "CAPCHA_NOT_READY":
print(f"FAILED: {result.text}")
return None
print("TIMEOUT after 5 minutes")
return None
Error 7: ada beberapa widget reCAPTCHA di satu halaman
Sebagian halaman memuat checkbox v2 yang terlihat DAN reCAPTCHA Invisible sekaligus. Jika Anda menyelesaikan yang keliru, tokennya memang valid tapi tidak cocok dengan widget yang menjaga aksi yang Anda butuhkan. Bedakan keduanya lewat dua penanda:
data-size:invisibleuntuk widget invisible,normal(atau kosong) untuk checkbox biasadata-callback: hampir selalu ada pada widget invisible, sering absen pada checkbox
Perbaikan — targetkan widget yang tepat. Kumpulkan semua widget di halaman, lalu pilih yang benar-benar invisible:
from bs4 import BeautifulSoup
def find_all_recaptcha_widgets(html):
soup = BeautifulSoup(html, "html.parser")
widgets = []
for el in soup.find_all(class_="g-recaptcha"):
widgets.append({
"sitekey": el.get("data-sitekey"),
"size": el.get("data-size", "normal"),
"callback": el.get("data-callback"),
"tag": el.name,
"id": el.get("id")
})
return widgets
# Example output:
# [
# {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
# {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve
Wrapper lengkap dengan penanganan error
Wrapper siap produksi berikut merangkum semua error di atas dalam satu kelas. Yang ditangani sekaligus:
- Retry otomatis dengan jeda yang meningkat pada error sementara
- Deteksi error konfigurasi (sitekey atau API key) yang tak perlu diulang
- Polling sampai token siap, lengkap dengan batas waktu
Tersedia dalam Python dan Node.js:
import requests
import time
import logging
logger = logging.getLogger(__name__)
class InvisibleRecaptchaSolver:
def __init__(self, api_key, max_retries=3):
self.api_key = api_key
self.max_retries = max_retries
self.base_url = "https://ocr.captchaai.com"
def solve(self, sitekey, page_url, proxy=None):
"""Solve invisible reCAPTCHA with automatic retry on transient errors."""
for attempt in range(1, self.max_retries + 1):
try:
token = self._attempt_solve(sitekey, page_url, proxy)
if token:
return token
except Exception as e:
logger.warning(f"Attempt {attempt} failed: {e}")
if attempt < self.max_retries:
time.sleep(2 ** attempt)
raise Exception(f"Failed to solve after {self.max_retries} attempts")
def _attempt_solve(self, sitekey, page_url, proxy):
params = {
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
}
if proxy:
params["proxy"] = proxy
params["proxytype"] = "HTTP"
# Submit task
resp = requests.get(f"{self.base_url}/in.php", params=params)
if "ERROR" in resp.text:
error = resp.text.strip()
if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
raise Exception(f"Configuration error (do not retry): {error}")
if error == "ERROR_ZERO_BALANCE":
raise Exception("Account balance is zero — add funds")
raise Exception(f"Submit error: {error}")
if not resp.text.startswith("OK|"):
raise Exception(f"Unexpected submit response: {resp.text}")
task_id = resp.text.split("|")[1]
# Poll for result
for _ in range(60):
time.sleep(5)
result = requests.get(f"{self.base_url}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id
})
if result.text.startswith("OK|"):
return result.text.split("|")[1]
if result.text == "CAPCHA_NOT_READY":
continue
if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
logger.warning("Captcha unsolvable — will retry with new task")
return None
raise Exception(f"Poll error: {result.text}")
raise Exception("Solve timed out after 5 minutes")
# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
sitekey="6LdKlZEU...",
page_url="https://staging.example.com/qa-login"
)
print(f"Token: {token[:50]}...")
const axios = require("axios");
class InvisibleRecaptchaSolver {
constructor(apiKey, maxRetries = 3) {
this.apiKey = apiKey;
this.maxRetries = maxRetries;
this.baseUrl = "https://ocr.captchaai.com";
}
async solve(sitekey, pageUrl, proxy) {
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const token = await this._attemptSolve(sitekey, pageUrl, proxy);
if (token) return token;
} catch (err) {
console.warn(`Attempt ${attempt} failed: ${err.message}`);
if (attempt < this.maxRetries) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
}
}
}
throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
}
async _attemptSolve(sitekey, pageUrl, proxy) {
const params = {
key: this.apiKey,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageUrl,
invisible: 1,
};
if (proxy) {
params.proxy = proxy;
params.proxytype = "HTTP";
}
// Submit task
const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
if (submitResp.data.includes("ERROR")) {
const error = submitResp.data.trim();
if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
throw new Error(`Configuration error (do not retry): ${error}`);
}
throw new Error(`Submit error: ${error}`);
}
const taskId = submitResp.data.split("|")[1];
// Poll for result
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "get", id: taskId },
});
if (result.data.startsWith("OK|")) {
return result.data.split("|")[1];
}
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
throw new Error(`Poll error: ${result.data}`);
}
throw new Error("Solve timed out after 5 minutes");
}
}
// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://staging.example.com/qa-login").then((token) => {
console.log(`Token: ${token.substring(0, 50)}...`);
});
Checklist troubleshooting
Telusuri langkah-langkah ini setiap kali solve reCAPTCHA Invisible gagal:
- Pastikan ini benar invisible, bukan v2 standar - cari
data-size="invisible"atausize: 'invisible'di render call. - Verifikasi sitekey dengan membandingkannya ke
data-sitekeypada widget invisible secara spesifik. - Pastikan
invisible=1ada di request API - cek parameterin.phpAnda. - Cek
pageurlsama persis; pakai URL dari DevTools browser, bukan URL redirect. - Temukan nama fungsi callback lewat atribut
data-callbackataucallbackdigrecaptcha.render(). - Pastikan token di-inject DAN callback dipanggil - token saja tidak cukup.
- Cek kesegaran token; token harus dipakai dalam 120 detik.
- Uji dengan proxy bila IP berpengaruh - tambahkan parameter
proxydanproxytype.
Pertanyaan Umum
Apakah CaptchaAI mendukung reCAPTCHA Invisible?
Ya. reCAPTCHA v2 Invisible termasuk tipe yang didukung penuh (GA). Anda memakai method yang sama dengan v2 (method=userrecaptcha), cukup menambahkan invisible=1 pada request.
Mengapa token berhasil didapat tapi form tetap gagal terkirim?
Hampir selalu karena callback tidak dipanggil. Pada reCAPTCHA Invisible, mengisi g-recaptcha-response saja tidak cukup — Anda harus memanggil fungsi data-callback dengan token tersebut agar form benar-benar diproses.
Berapa lama waktu menyelesaikan reCAPTCHA Invisible?
Umumnya 10–30 detik lewat CaptchaAI. Invisible challenge cenderung lebih cepat daripada checkbox v2 karena tidak butuh image recognition, melainkan mengandalkan risk analysis di sisi Google.
Kenapa solve lancar di lokal tapi gagal setelah di-deploy ke server?
Biasanya IP tidak cocok. Di laptop, IP Anda dan IP solver bisa mirip; setelah pindah ke server (misalnya region Jakarta atau Singapura), keduanya berbeda jauh. Tambahkan parameter proxy yang sesuai dengan IP sesi Anda untuk mengatasinya.
Bagaimana menargetkan widget yang tepat saat halaman punya beberapa reCAPTCHA?
Kumpulkan semua elemen g-recaptcha, lalu pilih yang data-size-nya invisible. Ambil data-sitekey dari widget itu secara spesifik, jangan dari checkbox v2 yang kebetulan ada di halaman yang sama.
Langkah selanjutnya
Lanjutkan dengan panduan yang berkaitan langsung: