Troubleshooting

Kesalahan dan Perbaikan Verifikasi Domain reCAPTCHA

Token reCAPTCHA valid, solve-nya sukses, tapi server tetap membalas success: false? Kemungkinan besar bukan CAPTCHA-nya yang bermasalah — melainkan domainnya. reCAPTCHA mengikat setiap token ke hostname tempat token itu dibuat; begitu pageurl yang dikirim ke solver tidak persis sama dengan domain tempat token dikirimkan, verifikasi domain menolaknya tanpa pesan error yang jelas.

Ringkasan Cepat: Gejala dan Perbaikannya

Rujukan cepat untuk gejala yang paling sering dilaporkan tim otomatisasi:

Gejala Kemungkinan penyebab Cara mendiagnosis Perbaikan
Token selalu ditolak pageurl tidak cocok domain target Bandingkan pageurl solver dengan domain pengiriman sebenarnya Perbarui pageurl agar sama persis
Berhasil di www, gagal di non-www Varian domain tidak cocok Cek perilaku redirect Pakai varian yang dipakai situs target
Kadang berhasil, kadang gagal CDN/load balancer melayani domain berbeda-beda Cek apakah domain berubah antar-request Pakai URL konsisten dari redirect chain
Sukses di browser, gagal di skrip Skrip mengirim dari origin berbeda Bandingkan address bar browser dengan pageurl skrip Samakan dengan URL final browser
Token Enterprise ditolak Binding project/domain salah Cek sitekey Enterprise cocok domain Periksa pengaturan domain di console Enterprise

Detail tiap skenario, lengkap dengan kode diagnosis dan perbaikannya, ada di bawah.


Kenapa reCAPTCHA Bisa Menolak Token yang Sebenarnya Valid

Akar masalahnya selalu sama: reCAPTCHA menyimpan hostname widget di dalam token, lalu server pemilik situs memutuskan apakah hostname itu diterima. Alurnya, dari pendaftaran domain sampai keputusan akhir Google:

Site owner registers reCAPTCHA → adds allowed domains (example.com, www.example.com)
    ↓
reCAPTCHA widget loads on example.com → matches allowed domain ✓
    ↓
Token generated with embedded hostname
    ↓
Server validates token via siteverify API
    ↓
Google checks: Does token hostname match allowed domains?
    ├─ YES → { "success": true, "hostname": "example.com" }
    └─ NO  → { "success": false, error or hostname mismatch }

Tiga titik yang ikut menentukan hasilnya:

  1. Widget di browser — hanya dimuat di domain terdaftar (opsional, bisa dinonaktifkan)
  2. Saat token dibuat — hostname yang tertanam di token harus sama dengan halaman asal
  3. Saat server memvalidasi — siteverify mengembalikan hostname, server Anda yang memutuskan apakah cocok

3 Error Verifikasi Domain yang Paling Sering Muncul

1. Hostname di respons siteverify tidak sesuai ekspektasi

{
    "success": true,
    "hostname": "subdomain.example.com",
    "challenge_ts": "2025-01-15T10:30:00Z"
}

Token-nya sah, tapi field hostname menunjukkan domain berbeda dari yang diharapkan. Banyak implementasi server menolak kondisi ini:

# Server-side validation that checks hostname
def validate_token(token, secret_key, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret_key, "response": token},
    ).json()

    if not result.get("success"):
        return False

    # This check causes failures when hostnames don't match
    if result.get("hostname") != expected_hostname:
        return False  # Domain mismatch!

    return True

Tiga penyebab tersering:

  1. Token di-solve untuk www.example.com, divalidasi di example.com
  2. Token di-solve untuk staging.example.com, divalidasi di example.com
  3. Proxy atau CDN mengubah hostname yang terlihat server

Perbaikan: pastikan pageurl di request solver Anda sama persis dengan domain tempat token itu nanti dikirimkan.

2. Widget reCAPTCHA menolak dimuat

Widget menampilkan error atau bahkan tidak render sama sekali:

ERROR: Invalid domain for site key

Penyebab yang biasa ditemukan:

  • Domain yang diizinkan di sitekey tidak mencakup domain halaman saat ini
  • Widget dimuat dari localhost atau protokol file://
  • Memakai alamat IP, bukan nama domain

Untuk otomatisasi: ini murni masalah konfigurasi pemilik situs. Yang bisa Anda kendalikan hanya memastikan pageurl yang dikirim ke solver cocok dengan domain yang diizinkan.

3. Token ditolak walau solve-nya sukses

{
    "success": false,
    "error-codes": ["invalid-input-response"]
}

Token dibuat untuk domain yang berbeda dari domain tempat ia divalidasi. Penyebab paling umum di skrip otomatisasi: pageurl yang dikirim ke solver tidak cocok dengan domain target sebenarnya.

# WRONG: pageurl doesn't match actual target
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": "https://staging.example.com/qa-login",  # ← Must match actual domain
    "json": 1,
})

# But submitting token to:
requests.post("https://app.staging.example.com/qa-login", ...)  # Different subdomain!

Aturan Pencocokan Domain: Exact Match vs Wildcard

Konfigurasi domain ada di tangan pemilik situs, bukan solver — tugas Anda hanya memastikan pageurl konsisten dengan domain yang sudah mereka daftarkan.

Verifikasi domain reCAPTCHA bukan pencocokan subdomain ketat secara default:

  • example.com biasanya menerima example.com, www.example.com, dan sub.example.com bila wildcard aktif
  • www.example.com saja hanya menerima www.example.com bila konfigurasinya ketat
  • *.example.com menerima subdomain apa pun dari example.com
  • localhost hanya menerima localhost, dipakai untuk development

Perilaku hostname di sisi server

Saat validasi lewat siteverify, field hostname di respons mencerminkan halaman tempat token dibuat — server pemilik situs yang menentukan apakah nilai itu diterima:

# Permissive validation (accepts any subdomain)
def validate_permissive(token, secret, base_domain):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    if not result.get("success"):
        return False

    hostname = result.get("hostname", "")
    return hostname == base_domain or hostname.endswith(f".{base_domain}")


# Strict validation (exact match only)
def validate_strict(token, secret, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    return result.get("success") and result.get("hostname") == expected_hostname

4 Cara Memperbaiki Error Domain di Skrip Otomatisasi

Skenario yang sering dialami tim scraping dan monitoring harga di Indonesia: worker jalan dari AWS ap-southeast-1 (Singapura) saat mengetes endpoint staging, token diterima tanpa masalah. Begitu dipindah ke production di belakang CDN, hostname yang terlihat oleh reCAPTCHA berubah dan token yang tadinya valid langsung ditolak. Empat langkah berikut urutan diagnosis yang efektif untuk kasus seperti ini.

Langkah 1: samakan pageurl dengan domain sebenarnya

Perbaikan paling sering dibutuhkan — pastikan pageurl sama persis dengan target aktual:

# Correct: pageurl matches where you'll submit the token
target_url = "https://www.staging.example.com/qa-login"

submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": target_url,  # Must match the actual domain
    "json": 1,
})

Langkah 2: tangani varian www vs non-www

Situs sering punya dua versi domain yang saling redirect — kode berikut mendeteksi versi mana yang sebenarnya dipakai:

from urllib.parse import urlparse

def normalize_url(url):
    """Normalize URL for consistent domain matching."""
    parsed = urlparse(url)
    # Use exactly what the target site uses
    # Check if the site redirects www → non-www or vice versa
    return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"

# Test which variant the site uses
response = requests.get("https://staging.example.com/qa-login", allow_redirects=True)
actual_url = response.url  # May be https://www.staging.example.com/qa-login after redirect

Langkah 3: ikuti redirect untuk menemukan domain asli

Beberapa situs mengalihkan lewat beberapa domain sebelum sampai ke halaman final:

def get_final_url(url):
    """Follow redirects to find the actual CAPTCHA page domain."""
    response = requests.get(url, allow_redirects=True, timeout=15)
    return response.url

# Login URL might redirect:
# https://staging.example.com/qa-login → https://auth.staging.example.com/qa-login
final_url = get_final_url("https://staging.example.com/qa-login")
# Use final_url as pageurl for solver

Langkah 4: ekstrak domain dari iframe reCAPTCHA

Kalau tiga langkah di atas belum cukup, ambil domain langsung dari markup iframe reCAPTCHA di halaman:

from bs4 import BeautifulSoup
from urllib.parse import urlparse

def extract_recaptcha_domain(html, page_url):
    """Extract the domain reCAPTCHA uses for token binding."""
    soup = BeautifulSoup(html, "html.parser")

    # Check for reCAPTCHA iframe
    iframe = soup.find("iframe", src=lambda s: s and "recaptcha" in s)
    if iframe:
        src = iframe.get("src", "")
        # The iframe URL may contain the domain parameter
        if "domain=" in src:
            # Extract domain from iframe URL
            pass

    # Default: use the page URL's domain
    return urlparse(page_url).netloc

Tool Diagnostik: Cek Domain Sebelum Solve

Daripada menebak, jalankan diagnostik kecil ini sebelum mengirim task ke solver — ia mengikuti redirect, mengecek varian www, dan langsung melaporkan pageurl yang seharusnya dipakai:

import requests
from urllib.parse import urlparse

class DomainDiagnostic:
    """Diagnose domain verification issues for reCAPTCHA solving."""

    def __init__(self, target_url):
        self.target_url = target_url
        self.issues = []

    def check_redirects(self):
        """Check if the URL redirects to a different domain."""
        try:
            response = requests.get(
                self.target_url, allow_redirects=True, timeout=15,
                headers={"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0"},
            )
            final_url = response.url
            original_domain = urlparse(self.target_url).netloc
            final_domain = urlparse(final_url).netloc

            if original_domain != final_domain:
                self.issues.append({
                    "type": "redirect",
                    "message": f"Redirects from {original_domain} to {final_domain}",
                    "fix": f"Use pageurl: {final_url}",
                })

            return final_url
        except Exception as e:
            self.issues.append({"type": "error", "message": str(e)})
            return self.target_url

    def check_www_variant(self):
        """Check if www and non-www point to the same content."""
        parsed = urlparse(self.target_url)
        domain = parsed.netloc

        if domain.startswith("www."):
            alt_domain = domain[4:]
        else:
            alt_domain = f"www.{domain}"

        alt_url = self.target_url.replace(domain, alt_domain)

        try:
            alt_response = requests.get(alt_url, allow_redirects=True, timeout=10)
            alt_final = urlparse(alt_response.url).netloc

            if alt_final != domain and alt_final != alt_domain:
                self.issues.append({
                    "type": "www_redirect",
                    "message": f"{alt_domain} redirects to {alt_final}",
                })
        except Exception:
            pass

    def report(self):
        """Generate diagnostic report."""
        final_url = self.check_redirects()
        self.check_www_variant()

        print(f"Target URL: {self.target_url}")
        print(f"Final URL:  {final_url}")
        print(f"Use as pageurl: {final_url}")

        if self.issues:
            print("\nIssues found:")
            for issue in self.issues:
                print(f"  [{issue['type']}] {issue['message']}")
                if "fix" in issue:
                    print(f"  Fix: {issue['fix']}")
        else:
            print("\nNo domain issues detected.")


# Usage
diag = DomainDiagnostic("https://staging.example.com/qa-login")
diag.report()

Pertanyaan yang Sering Diajukan

Kenapa token reCAPTCHA saya lolos saat testing di localhost tapi ditolak begitu masuk production?

Empat penyebab tersering: localhost punya aturan domain sendiri, production dilayani CDN dengan hostname berbeda dari perkiraan, validasi hostname di production biasanya lebih ketat, dan redirect chain-nya sering berubah.

Apakah http dan https dianggap domain yang berbeda oleh reCAPTCHA?

Tidak. Verifikasinya berbasis hostname, bukan skema URL — http://example.com dan https://example.com dianggap sama selama hostname-nya identik. Token biasanya ditolak karena subdomain atau redirect, bukan skemanya.

Bagaimana cara memastikan pageurl yang saya kirim ke CaptchaAI sudah sesuai domain target sebenarnya?

Jalankan tool diagnostik di atas sebelum mengirim task solve — ia mengikuti redirect dan melaporkan URL final yang sebaiknya dipakai sebagai pageurl, jauh lebih cepat daripada menebak.

Token yang sudah di-solve untuk satu subdomain, bisakah dipakai di subdomain lain?

Tidak. Token reCAPTCHA terikat ke hostname tempat ia dibuat. Token untuk app.example.com tidak otomatis berlaku di checkout.example.com — subdomain yang mirip pun bisa gagal jika situs memvalidasi hostname secara ketat.

Apa yang harus dilakukan kalau CDN mengubah domain yang terlihat oleh reCAPTCHA?

Cari tahu hostname yang sebenarnya sampai ke browser pengguna akhir — bukan hostname origin di belakang CDN — lalu pakai itu sebagai pageurl. Tool diagnostik di atas dan langkah 3 cara paling praktis menemukannya.


Ringkasan

Verifikasi domain reCAPTCHA mengikat setiap token ke hostname tempat ia dibuat. Kegagalan otomatisasi paling sering adalah ketidakcocokan pageurl — URL yang dikirim ke CaptchaAI harus sama persis dengan domain tempat token nanti dikirimkan. Ikuti redirect untuk menemukan domain sebenarnya, tangani varian www vs non-www, dan jalankan tool diagnostik sebelum solve — bukan sesudah token ditolak.

Artikel Terkait

Komentar dinonaktifkan untuk artikel ini.