API Tutorials

Parameter Halaman Cloudflare Challenge dan Aliran Token

Kalau automation Anda disambut halaman "Just a moment..." dari Cloudflare, jawabannya ada di parameter tersembunyi di HTML-nya: Ray ID, md, r, dan objek chl_opt yang menjalankan proof-of-work di balik layar. Begitu lolos, Cloudflare menerbitkan cookie qa_validation_cookie sebagai kunci akses berikutnya — paham peran tiap parameter bikin diagnosis solving gagal jauh lebih cepat.


Isi Halaman Challenge Cloudflare (HTTP 503)

Isi HTML halaman challenge Cloudflare (HTTP 503):

<!DOCTYPE html>
<html>
<head>
    <title>Just a moment...</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
    <div id="challenge-stage">
        <div id="challenge-body-text">
            Checking if the site connection is secure
        </div>
        <div id="challenge-spinner">
            <!-- Loading spinner -->
        </div>
    </div>

    <div id="challenge-form" style="display:none">
        <form id="challenge-form" action="/..." method="POST">
            <!-- Hidden parameters -->
            <input type="hidden" name="md" value="...">
            <input type="hidden" name="r" value="...">
        </form>
    </div>

    <script src="/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=...">
    </script>
</body>
</html>

Parameter Penting di Balik Layar

Parameter di Halaman Challenge

Parameter Nama Fungsi
ray Cloudflare Ray ID Pengenal unik, menghubungkan challenge ke request asli
md Metadata challenge Status challenge terenkripsi
r Token respons Jawaban hitungan, diisi JavaScript
chl_opt Opsi challenge Konfigurasi skrip challenge
cRay Ray challenge (sekunder) Ray ID kedua untuk pelacakan
cZone Zona challenge ID zona Cloudflare
cUPMDTk Stempel waktu Waktu challenge diterbitkan
cHash Hash challenge Validasi integritas

Parameter di URL Skrip dan Payload JavaScript

URL skrip challenge-nya:

/cdn-cgi/challenge-platform/h/g/orchestrate/chl_page/v1?ray=ABC123

Pecahannya begini:

  • /cdn-cgi/challenge-platform/ — infrastruktur challenge milik Cloudflare
  • h/g/ — versi/varian challenge
  • orchestrate/ — endpoint orkestrasi challenge
  • chl_page/v1 — versi halaman challenge
  • ray=ABC123 — Ray ID yang terikat ke request ini

Skrip challenge-nya sendiri memuat parameter tambahan lewat objek _cf_chl_opt:

// Extracted from obfuscated challenge script
window._cf_chl_opt = {
    cvId: '2',           // Challenge version
    cType: 'managed',    // Challenge type
    cNounce: '...',      // Cryptographic nonce
    cRay: '...',         // Challenge Ray ID
    cHash: '...',        // Challenge hash
    cUPMDTk: '...',      // Timestamp
    cFPWv: 'g',          // sinyal browser version
    cTTimeMs: '4000',    // Minimum wait time (ms)
    cTplV: 5,            // Template version
    cLt: '...',          // Challenge lifetime
    cRq: {},             // Challenge request data
};

Cara Kerja Alur Token: dari Challenge sampai Akses Diberikan

Urutannya dari request pertama sampai konten muncul:


1. CLIENT → CLOUDFLARE EDGE
   GET /protected-page
   ↓

2. CLOUDFLARE → CLIENT
   HTTP 503 + Challenge page HTML
   Sets: __cf_bm cookie (bot management tracking)
   Contains: ray ID, challenge script URL
   ↓

3. CLIENT (browser)
   Loads challenge script from /cdn-cgi/challenge-platform/...
   ↓

4. CHALLENGE SCRIPT EXECUTES:
   a. Collects browser sinyal browser:

      - Canvas hash
      - WebGL renderer
      - Screen dimensions
      - Installed fonts
      - Timezone
      - Language
   b. Runs proof-of-work:

      - Iterates hash computations
      - Must find answer matching difficulty
   c. Computes timing:

      - Enforces minimum wait (cTTimeMs)
      - Records actual timing
   d. Generates response token:

      - Combines sinyal browser + PoW answer + timing
      - Encrypts with challenge nonce
   ↓

5. CLIENT → CLOUDFLARE
   POST /cdn-cgi/challenge-platform/h/g/flow/ov1/...
   Body: { r: "encrypted_response", md: "metadata", ... }
   ↓

6. CLOUDFLARE validates:
   - Proof-of-work answer correct?
   - Timing within acceptable range?
   - sinyal browser consistent with real browser?
   - No replay (nonce check)?
   ↓

7. CLOUDFLARE → CLIENT
   HTTP 200 + Set-Cookie: qa_validation_cookie=...; path=/; expires=...
   + HTTP redirect to original URL
   ↓

8. CLIENT → CLOUDFLARE
   GET /protected-page
   Cookie: qa_validation_cookie=...
   ↓

9. CLOUDFLARE → CLIENT
   HTTP 200 + Protected content

Garis waktu cookie-nya, disederhanakan:

Request 1: No cookies
    → Challenge page (503)
    → __cf_bm cookie set

Challenge solve:
    → qa_validation_cookie cookie set

Request 2+: qa_validation_cookie + __cf_bm
    → Content served (200)

After ~30 mins: qa_validation_cookie expires
    → Next request triggers new challenge

Empat cookie yang terlibat:

  • __cf_bm — tracking sesi bot management, lifetime 30 menit, scope domain
  • qa_validation_cookie — bukti izin challenge, lifetime 15 menit–24 jam, scope domain
  • __cflb — afinitas load balancer, lifetime sesi, scope domain
  • _cfuvid — ID pengunjung unik, lifetime sesi, scope domain

Cookie qa_validation_cookie cuma valid kalau tiga hal ini konsisten:

  1. Alamat IP — sama dengan yang menyelesaikan challenge
  2. User-Agent — identik dengan UA saat solving
  3. Domain — hanya berlaku untuk domain penerbitnya
# ❌ FAILS — IP mismatch
# Solve challenge from IP A, then use qa_validation_cookie from IP B

# ❌ FAILS — UA mismatch
# Solve with Chrome UA, then send requests with Firefox UA

# ✅ WORKS — Same IP + Same UA
session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 ... Chrome/120.0.0.0"
# Use same session for solving and subsequent requests

Ekstrak Manual vs Solve Otomatis dengan CaptchaAI

Mau bangun parser sendiri? Contoh ambil Ray ID dan opsi challenge-nya.

Python:

import re
import requests

def extract_challenge_params(url):
    """Extract Cloudflare challenge page parameters."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }

    response = requests.get(url, headers=headers, timeout=15, allow_redirects=False)
    html = response.text

    params = {
        "status_code": response.status_code,
        "cf_ray": response.headers.get("cf-ray", ""),
        "is_challenge": response.status_code == 503,
    }

    if not params["is_challenge"]:
        return params

    # Extract Ray ID from page
    ray_match = re.search(r"ray['\"]?\s*[:=]\s*['\"]([a-f0-9]+)['\"]", html, re.I)
    if ray_match:
        params["ray_id"] = ray_match.group(1)

    # Extract challenge script URL
    script_match = re.search(
        r'src=["\'](/cdn-cgi/challenge-platform/[^"\']+)["\']', html
    )
    if script_match:
        params["challenge_script"] = script_match.group(1)

    # Extract challenge options
    opt_match = re.search(r"_cf_chl_opt\s*=\s*\{([^}]+)\}", html)
    if opt_match:
        opt_text = opt_match.group(1)

        # Parse individual options
        for key in ["cType", "cRay", "cHash", "cTTimeMs", "cvId", "cFPWv"]:
            val_match = re.search(
                rf"{key}\s*:\s*['\"]?([^'\"', }}]+)", opt_text
            )
            if val_match:
                params[key] = val_match.group(1)

    # Extract form parameters
    md_match = re.search(r'name=["\']md["\']\s+value=["\']([^"\']+)["\']', html)
    if md_match:
        params["md"] = md_match.group(1)

    # Extract cookies from response
    params["cookies"] = {
        name: value
        for name, value in response.cookies.items()
    }

    return params


# Usage
params = extract_challenge_params("https://protected-site.com")
if params["is_challenge"]:
    print(f"Challenge type: {params.get('cType', 'unknown')}")
    print(f"Ray ID: {params.get('ray_id', params['cf_ray'])}")
    print(f"Min wait: {params.get('cTTimeMs', '?')}ms")
    print(f"Script: {params.get('challenge_script', 'not found')}")

Node.js:

const axios = require("axios");

async function extractChallengeParams(url) {
  const response = await axios.get(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 Chrome/120.0.0.0",
      Accept: "text/html,*/*;q=0.8",
    },
    validateStatus: () => true,
    maxRedirects: 0,
  });

  const html = response.data;
  const params = {
    statusCode: response.status,
    cfRay: response.headers["cf-ray"] || "",
    isChallenge: response.status === 503,
  };

  if (!params.isChallenge) return params;

  // Extract challenge script URL
  const scriptMatch = html.match(
    /src=["'](\/cdn-cgi\/challenge-platform\/[^"']+)["']/
  );
  if (scriptMatch) params.challengeScript = scriptMatch[1];

  // Extract challenge type
  const typeMatch = html.match(/cType\s*:\s*['"]?(\w+)/);
  if (typeMatch) params.challengeType = typeMatch[1];

  // Extract timing
  const timeMatch = html.match(/cTTimeMs\s*:\s*['"]?(\d+)/);
  if (timeMatch) params.minWaitMs = parseInt(timeMatch[1]);

  return params;
}

extractChallengeParams("https://protected-site.com").then(console.log);

Kalau Anda tidak mau merawat parser sendiri, CaptchaAI menjalankan seluruh alur ini secara internal — kirim URL target, ambil hasilnya:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_cloudflare_challenge(target_url):
    """Solve Cloudflare challenge page — CaptchaAI handles token flow."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "cloudflare_challenge",
        "sitekey": "managed",
        "pageurl": target_url,
        "json": 1,
    })

    task_id = submit.json()["request"]

    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,
            "json": 1,
        }).json()

        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("Challenge solve timed out")


# CaptchaAI handles the full flow:
# 1. Loads the challenge page
# 2. Executes JavaScript
# 3. Solves proof-of-work
# 4. Returns clearance token/cookies
token = solve_cloudflare_challenge("https://protected-site.com/login")

Kenapa Ini Penting untuk Tim Otomasi di Indonesia

Buat tim scraping/automation Indonesia (banyak kerja lepas atau agency price-monitoring), Cloudflare Challenge sering jadi titik gagal termahal — tiap retry butuh proof-of-work baru. Server di AWS ap-southeast-3 (Jakarta) pun kena latensi ke edge Cloudflare, jadi longgarkan timeout sebelum menyalahkan skrip. Kalau targetnya data pribadi, ingat UU PDP (UU 27/2022): proses hanya data yang berwenang Anda akses.

Lima masalah paling umum yang bikin solving gagal, sebelum masuk debug lebih dalam:

  1. Tipe challenge "managed" tapi solving tetap gagal → situs butuh Turnstile, bukan JS challenge. Coba method turnstile, bukan cloudflare_challenge.
  2. qa_validation_cookie jalan sekali lalu ditolak → rotasi IP mengubah IP Anda. Pin IP sepanjang durasi izin.
  3. Halaman "Just a moment..." tidak pernah selesai → JavaScript diblokir atau format salah. Pakai CaptchaAI, bukan solving manual.
  4. Challenge muncul lagi tiap request → qa_validation_cookie tidak ikut terkirim. Pastikan cookie persisten dalam session.
  5. Challenge beda tiap path URL → aturan WAF per path berbeda. Solve terpisah untuk tiap path.

Debug Kalau Solving Gagal

Titik-titik yang paling sering bikin automation macet:

  • Halaman challenge tidak termuat — timeout/respons kosong, biasanya masalah jaringan atau proxy
  • Skrip gagal dieksekusi — challenge berulang, API JavaScript tidak tersedia
  • Proof-of-work gagal — spinner tanpa henti, timeout komputasi
  • Respons ditolak — redirect balik ke challenge, timing salah atau sinyal browser tidak cocok
  • qa_validation_cookie tidak ter-set — cookie hilang setelah solve, error parsing respons
  • qa_validation_cookie ditolak — 403 di request berikutnya, IP atau UA tidak cocok

Script checklist untuk menelusurinya langkah demi langkah:

def debug_challenge_flow(url, cf_clearance_cookie=None, user_agent=None):
    """Debug the challenge solve flow step by step."""
    ua = user_agent or (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    steps = []

    # Step 1: Initial request
    response = requests.get(
        url,
        headers={"User-Agent": ua, "Accept": "text/html,*/*;q=0.8"},
        timeout=15,
        allow_redirects=False,
    )
    steps.append({
        "step": "initial_request",
        "status": response.status_code,
        "is_challenge": response.status_code == 503,
        "cf_ray": response.headers.get("cf-ray", ""),
    })

    # Step 2: Test with qa_validation_cookie
    if cf_clearance_cookie:
        session = requests.Session()
        session.cookies.set("qa_validation_cookie", cf_clearance_cookie)
        session.headers["User-Agent"] = ua

        response2 = session.get(url, timeout=15, allow_redirects=False)
        steps.append({
            "step": "with_clearance",
            "status": response2.status_code,
            "passed": response2.status_code == 200,
        })

        if response2.status_code != 200:
            steps.append({
                "step": "diagnosis",
                "issue": "qa_validation_cookie rejected",
                "possible_causes": [
                    "Cookie expired",
                    "IP address changed",
                    "User-Agent mismatch",
                    "Cookie from different domain",
                ],
            })

    return steps

FAQ Seputar Parameter dan Token Cloudflare Challenge

Apa bedanya parameter md dan r di form challenge?

md adalah metadata challenge terenkripsi dari server. r baru terisi setelah JavaScript menghitung proof-of-work. Kalau r salah, submit ditolak dan Anda dilempar ke challenge baru.

Tergantung operator situs — default 30 menit, rentang 15 menit sampai 24 jam untuk enterprise. Kalau automation jalan lebih lama tanpa request baru, siapkan ulang alur solve-nya.

Kenapa proses solve saya sukses tapi request berikutnya tetap kena 403?

Biasanya IP atau User-Agent request berikutnya beda dari saat solving — cookie ini terikat ke keduanya. Sering terjadi kalau proxy mengganti IP di tengah sesi. Pin IP dan UA yang sama sepanjang sesi.

Apakah saya wajib parsing Ray ID dan chl_opt sendiri kalau pakai CaptchaAI?

Tidak. CaptchaAI menjalankan semuanya secara internal, dari memuat halaman sampai mengembalikan token. Kirim URL lewat in.php, ambil hasil lewat res.php; parsing manual di atas cuma perlu kalau membangun solver sendiri.

Tidak. Cookie ini di-scope ke domain penerbitnya — tiap domain, termasuk subdomain lain, butuh solve dan cookie izinnya sendiri.


Intinya

Halaman Cloudflare Challenge membawa parameter yang saling terkait — Ray ID, md, r, chl_opt — yang menjalankan proof-of-work di browser sebelum Cloudflare menerbitkan qa_validation_cookie (terikat IP dan User-Agent, berlaku 15 menit–24 jam). Paham perannya bikin Anda tahu persis di titik mana automation gagal: parsing HTML, eksekusi JavaScript, atau validasi cookie. Kalau tidak mau membangun dan merawat parser sendiri, CaptchaAI menjalankan seluruh alurnya otomatis lewat API.

Baca Juga

Komentar dinonaktifkan untuk artikel ini.