Khắc Phục Sự Cố

Lỗi Cloudflare Turnstile và cách khắc phục

Trường hợp gây bối rối nhất với Cloudflare Turnstile là khi API báo giải xong, trả về một token trông hoàn toàn hợp lệ, nhưng trang đích vẫn từ chối. Turnstile hiếm khi lỗi ngẫu nhiên: gần như mọi sự cố đều rơi vào một trong ba giai đoạn rõ ràng, và xác định đúng giai đoạn là nửa đường tới cách sửa.

  • Giai đoạn gửi request — request lên in.php bị từ chối (sai key, thiếu pageurl, sai tham số).
  • Giai đoạn polling — vòng lặp đọc kết quả từ res.php không trả về token hoặc hết thời gian chờ.
  • Giai đoạn xác thực trang đích — API trả về token hợp lệ nhưng trang vẫn không chấp nhận.

Ba giai đoạn này cũng chính là thứ tự bạn nên gỡ lỗi: chỉ khi request được chấp nhận thì mới có nghĩa để bàn tới polling, và chỉ khi đã có token hợp lệ thì mới xét chuyện trang đích từ chối. Nhảy cóc giữa các giai đoạn là lý do phổ biến khiến một lỗi Turnstile bị gỡ nhầm chỗ hàng giờ.

Ba nguyên nhân đặc trưng của Turnstile khiến giai đoạn cuối hay hỏng nhất là:

  1. Sai pageurl chính xác — nhất là trên các trang Cloudflare Challenge, nơi ngữ cảnh trang bị kiểm tra chặt hơn
  2. Sai sitekey — lấy nhầm từ phần tử khác hoặc từ một phiên bản widget khác trên cùng trang
  3. Gắn token sai đường — trang chờ token ở cf-turnstile-response, ở callback, hoặc cả hai

CaptchaAI giải Turnstile với tỷ lệ giải thành công cao và ổn định trong dưới 10 giây. Khi tích hợp của bạn hỏng, thủ phạm gần như luôn là tham số bạn gửi lên hoặc cách bạn áp dụng token trả về — không phải chất lượng lời giải.


Ba điều khiến Turnstile khác các loại CAPTCHA khác

Trước khi tra mã lỗi, cần nắm ba đặc điểm khiến Turnstile "khó chiều" hơn phần lớn các loại CAPTCHA khác.

pageurl phải khớp chính xác

Token Turnstile gắn chặt với ngữ cảnh của trang. Trên các trang Cloudflare Challenge (màn hình xác minh toàn trang), chỉ cần dùng sai URL — kể cả lệch một đoạn path — là token sẽ bị từ chối. Đây là lỗi hay gặp nhất trong nhóm khó chẩn đoán.

Token gắn theo hai đường: trường ẩn hoặc callback

Token trả về có thể được áp dụng theo hai cách. Chọn nhầm đường thì form sẽ hỏng mà không báo lỗi rõ ràng:

Cách gắn token Khi nào dùng
Trường ẩn — ghi vào cf-turnstile-response (và đôi khi cả g-recaptcha-response) Khi trang dùng form chuẩn có input ẩn
Hàm callback — gọi hàm khai báo trong turnstile.render() hoặc data-callback Khi trang xác thực bằng code thay vì gửi form trực tiếp

Token chỉ dùng được một lần

Mỗi token Turnstile chỉ xác minh được đúng một lần. Nếu automation lỡ gửi nó hai lần, hoặc có race condition giữa các bước, thì lần thứ hai chắc chắn thất bại.


Lỗi giai đoạn gửi request

Nhóm này xuất hiện khi bạn gửi task tới https://ocr.captchaai.com/in.php.

ERROR_WRONG_USER_KEY

  • Nguyên nhân: Định dạng API key sai (phải đủ 32 ký tự).
  • Cách sửa: Kiểm tra lại key tại captchaai.com/api.php.

ERROR_KEY_DOES_NOT_EXIST

  • Nguyên nhân: Key đúng định dạng nhưng không gắn với tài khoản đang hoạt động.
  • Cách sửa: Mở dashboard, xác nhận tài khoản còn hoạt động và key được sao chép chính xác.

ERROR_ZERO_BALANCE

  • Nguyên nhân: Không còn thread rảnh trong gói của bạn.
  • Cách sửa: Chờ thread giải phóng, giảm số request đồng thời, hoặc nâng cấp gói. Mỗi thread là một lời giải đang chạy; khi một task xong, thread đó lập tức nhận task kế tiếp, nên thường chỉ cần giảm số task gửi cùng lúc là hết lỗi này mà chưa cần đổi gói.

ERROR_PAGEURL

  • Nguyên nhân: Thiếu tham số pageurl.
  • Cách sửa: Gửi URL đầy đủ — gồm giao thức, domain và path:
pageurl=https://staging.example.com/qa-login

ERROR_BAD_PARAMETERS

Nguyên nhân: Thiếu tham số bắt buộc hoặc sai định dạng. Với Turnstile, các tham số bắt buộc gồm:

Tham số Kiểu Bắt buộc Mô tả
key Chuỗi API key CaptchaAI của bạn
method Chuỗi Phải là turnstile
sitekey Chuỗi Sitekey của widget Turnstile
pageurl Chuỗi URL đầy đủ của trang

Tùy chọn nhưng nên có:

Tham số Kiểu Mô tả
action Chuỗi Giá trị data-action hoặc tham số action trong turnstile.render()
proxy Chuỗi Định dạng: login:password@IP:PORT
proxytype Chuỗi HTTP, HTTPS, SOCKS4, SOCKS5

Cách sửa: Kiểm tra mọi trường bắt buộc đều có mặt và đúng kiểu dữ liệu.

Phản hồi HTML hoặc mã 500/502

  • Nguyên nhân: Lỗi tạm thời phía máy chủ.
  • Cách sửa: Chờ 5–10 giây rồi thử lại.

Tìm đúng sitekey của Turnstile

Sitekey là tham số hay bị sai nhất. Dưới đây là ba cách lấy nó cho chắc.

Cách 1 — thuộc tính data-sitekey:

<div class="cf-turnstile" data-sitekey="0x4AAAAAAAB1example"></div>

Cách 2 — lời gọi turnstile.render():

turnstile.render('#captcha-container', {
  sitekey: '0x4AAAAAAAB1example',
  callback: function(token) {
    document.getElementById('cf-turnstile-response').value = token;
  }
});

Cách 3 — chặn lời gọi render (nâng cao):

Nếu sitekey được nạp động, bạn có thể định nghĩa lại turnstile.render trước khi widget khởi tạo để bắt tham số:

// Inject this before the Turnstile script loads
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);
};

Lỗi giai đoạn polling

Nhóm này xuất hiện khi bạn polling kết quả từ https://ocr.captchaai.com/res.php.

CAPCHA_NOT_READY

  • Đây không phải lỗi. Lời giải vẫn đang chạy. Turnstile tại CaptchaAI thường giải xong trong dưới 10 giây.
  • Cách sửa: Chờ 5 giây rồi polling lại.

ERROR_WRONG_ID_FORMAT

  • Nguyên nhân: ID task chứa ký tự không phải số.
  • Cách sửa: Dùng đúng ID mà in.php trả về, không chỉnh sửa gì.

ERROR_WRONG_CAPTCHA_ID

  • Nguyên nhân: ID không khớp với bất kỳ task nào đã gửi.
  • Cách sửa: Kiểm tra lại xem bạn có đang polling đúng ID lấy từ phản hồi lúc gửi task không.

ERROR_EMPTY_ACTION

  • Nguyên nhân: Thiếu tham số action trong request polling.
  • Cách sửa: Luôn kèm action=get:
https://ocr.captchaai.com/res.php?key=YOUR_KEY&action=get&id=CAPTCHA_ID&json=1

Lưu ý: Với Turnstile, luôn dùng json=1 khi polling. Phản hồi JSON có thể kèm user_agent của bộ giải — một số trang được Cloudflare bảo vệ cần đúng user_agent này thì mới xác thực token thành công.

Nếu phản hồi trả về user_agent, hãy dùng đúng chuỗi đó cho trình duyệt hoặc HTTP client khi bạn nộp token lên trang đích. Turnstile ràng buộc token với phiên đã giải, nên một user_agent lệch so với lúc giải là nguyên nhân âm thầm khiến trang từ chối dù token vẫn còn hạn.

ERROR_CAPTCHA_UNSOLVABLE

  • Nguyên nhân: Giải thất bại — thường do sai sitekey hoặc cấu hình trang không được hỗ trợ.
  • Cách sửa: Kiểm tra lại sitekey, lấy request mới và thử lại.

ERROR_INTERNAL_SERVER_ERROR

  • Nguyên nhân: Sự cố phía máy chủ.
  • Cách sửa: Chờ 10 giây rồi thử lại.

Lỗi xác thực trang đích: token hợp lệ nhưng trang từ chối

Đây là nhóm khó gỡ nhất, vì API đã trả về token thành công nhưng trang đích vẫn không chấp nhận.

Ví dụ hay gặp với các đội QA ở Việt Nam: một nhóm kiểm thử tại công ty product hoặc agency dựng lại luồng đăng nhập có Turnstile trên staging.example.com/qa-login để chạy hồi quy. Lời giải trả về token hợp lệ, log CaptchaAI báo status=1, nhưng form cứ nhảy lại trang đăng nhập. Thủ phạm gần như luôn là một trong bốn lỗi dưới đây — và phổ biến nhất là pageurl trỏ vào URL hiển thị trên trình duyệt thay vì URL thật sự nạp widget Turnstile.

Lỗi 1: token ghi vào sai trường

Triệu chứng: Form gửi đi nhưng trang báo lỗi xác thực hoặc tải lại.

Các trang Turnstile có thể chờ token ở những trường khác nhau:

  • cf-turnstile-response — input ẩn chính của Turnstile
  • g-recaptcha-response — một số trang dùng trường này như phương án dự phòng

Cách sửa: Kiểm tra form của trang xem có cả hai trường không. Trong automation trình duyệt:

# 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)

Lỗi 2: callback không được kích hoạt

  • Triệu chứng: Token đã có trong trường, nhưng form vẫn chặn không cho gửi.
  • Nguyên nhân: Trang dùng hàm callback thay cho (hoặc bổ sung cho) trường ẩn. Callback xử lý phần logic đi kèm như bật nút gửi hoặc bắn một request AJAX.
  • Cách sửa: Tìm và gọi đúng callback:
// Check data-callback attribute
const callbackName = document.querySelector('.cf-turnstile').getAttribute('data-callback');
if (callbackName && window[callbackName]) {
  window[callbackName](token);
}

// Or if it was passed in turnstile.render()
// You may need to intercept the render call to capture it

Lỗi 3: sai ngữ cảnh trang chính xác

  • Triệu chứng: Token bị từ chối dù sitekey đúng và lời giải còn mới.
  • Nguyên nhân: pageurl gửi lên API không khớp ngữ cảnh thực tế của trang. Rất hay xảy ra trên:

  • Trang Cloudflare Challenge — URL có thể chứa query parameter hoặc đoạn path quan trọng

  • Single-page application — URL hiển thị có thể khác URL đã nạp widget Turnstile

Cách sửa: Mở tab Network trong DevTools để tìm đúng URL mà widget Turnstile nạp từ đó, rồi dùng URL đó làm pageurl. Với single-page application, hãy lấy URL tại thời điểm widget khởi tạo chứ không phải URL sau khi người dùng điều hướng tiếp — hai giá trị này thường khác nhau và chỉ giá trị đầu mới khớp ngữ cảnh của token.

Lỗi 4: dùng lại token

  • Triệu chứng: Lời giải đầu chạy được, các lần sau đều lỗi.
  • Nguyên nhân: Token Turnstile chỉ dùng một lần. Sau khi máy chủ Cloudflare xác minh, token lập tức bị vô hiệu.
  • Cách sửa: Lấy lời giải mới cho mỗi lần gửi form. Không cache, không dùng lại token cũ.

Khi đã làm đúng mọi thứ mà token vẫn hỏng

Nếu bạn đã xác minh sitekey, pageurl, đường gắn token và token còn mới mà trang vẫn từ chối, khả năng cao bạn không gặp một widget Turnstile nhúng mà là một trang Cloudflare Challenge toàn trang. Hai thứ này trông na ná nhau nhưng cần method API khác nhau (turnstile so với cloudflare_challenge) và trả về kiểu kết quả khác nhau — token so với cookie phiên. Phần so sánh bên dưới giúp bạn phân biệt trước khi tốn thêm thời gian gỡ nhầm chỗ.


Bảng tra nhanh: lỗi → cách sửa

Lỗi/Triệu chứng Giai đoạn Nguyên nhân thường gặp Cách sửa
ERROR_WRONG_USER_KEY Gửi request API key sai định dạng Kiểm tra key 32 ký tự
ERROR_KEY_DOES_NOT_EXIST Gửi request Key không hợp lệ Kiểm tra dashboard
ERROR_ZERO_BALANCE Gửi request Hết thread rảnh Chờ hoặc nâng cấp gói
ERROR_PAGEURL Gửi request Thiếu pageurl Gửi URL đầy đủ
ERROR_BAD_PARAMETERS Gửi request Thiếu sitekey, method hoặc pageurl Kiểm tra mọi trường bắt buộc
CAPCHA_NOT_READY Polling Đang giải Chờ 5 giây, thử lại
ERROR_WRONG_ID_FORMAT Polling ID task không phải số Dùng đúng ID từ in.php
ERROR_WRONG_CAPTCHA_ID Polling ID task không hợp lệ Kiểm tra ID lúc gửi task
ERROR_EMPTY_ACTION Polling Thiếu action=get Thêm tham số action
Token bị trang từ chối Xác thực Sai trường, callback không chạy, sai URL Kiểm tra tên trường, gọi callback, xác minh đúng pageurl
Lần giải thứ hai lỗi Xác thực Dùng lại token Lấy token mới cho mỗi lần gửi

Python: giải Turnstile hoàn chỉnh

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

Node.js: giải Turnstile hoàn chỉnh

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);

Turnstile hay Cloudflare Challenge: bạn đang gặp cái nào?

Nhiều lỗi "không sửa được" thực ra đến từ việc nhầm hai sản phẩm khác nhau của Cloudflare. Dưới đây là cách phân biệt nhanh:

Tín hiệu Turnstile Cloudflare Challenge
Bạn nhìn thấy gì Widget nhúng trong trang (ô tích hoặc ẩn) Màn hình xác minh Cloudflare toàn trang
CaptchaAI trả về gì Token để chèn vào form Một cookie <staging-session-cookie>
Method API turnstile cloudflare_challenge
Cần proxy? Tùy chọn Có (bắt buộc)

Nếu bạn đang đối mặt với thử thách Cloudflare toàn trang (không phải widget nhúng), bạn cần bộ giải Cloudflare Challenge — nó trả về cookie <staging-session-cookie> và yêu cầu proxy.


Câu hỏi thường gặp

Làm sao biết lỗi Turnstile của tôi nằm ở giai đoạn nào?

Nhìn vào nơi request dừng lại. Nếu in.php trả về mã ERROR_* ngay lúc gửi, đó là giai đoạn request. Nếu res.php cứ trả CAPCHA_NOT_READY mãi hoặc trả mã lỗi, đó là giai đoạn polling. Nếu API trả token status=1 mà trang đích vẫn từ chối, đó là giai đoạn xác thực — nhóm khó gỡ nhất. Hãy ghi lại mã lỗi cùng giai đoạn trước khi thử bất kỳ cách sửa nào; đoán mò cách sửa khi chưa biết mình đang ở đâu là lý do phổ biến khiến một lỗi Turnstile kéo dài nhiều giờ.

Giải một Turnstile mất bao lâu và tốn bao nhiêu thread?

Turnstile tại CaptchaAI thường giải xong trong dưới 10 giây và chiếm một thread trong lúc đang giải. Vì CaptchaAI tính theo thread (giá theo thread, không tính theo lượt giải), mỗi task xong sẽ trả thread về ngay cho task kế tiếp; gói BASIC ($15/tháng, 5 thread) cho phép 5 task Turnstile chạy song song.

Có cần proxy khi giải Turnstile không?

Với widget Turnstile độc lập thì proxy là tùy chọn. Nhưng trên trang Cloudflare Challenge toàn trang thì nên dùng proxy — thêm proxyproxytype vào request. Đây cũng là lý do nhiều lỗi "token bị từ chối" biến mất khi bạn chuyển sang bộ giải Cloudflare Challenge kèm proxy.

ERROR_CAPTCHA_UNSOLVABLE với Turnstile nghĩa là gì?

Nghĩa là lời giải thất bại, thường do sai sitekey hoặc trang có cấu hình không được hỗ trợ. Hãy lấy lại sitekey từ data-sitekey hoặc turnstile.render(), kiểm tra pageurl, rồi gửi request mới. Đừng polling lại cùng một ID — task đó đã kết thúc.

Vì sao lời giải đầu tiên chạy được nhưng các lần sau lại lỗi?

Vì token Turnstile chỉ dùng được một lần. Sau khi Cloudflare xác minh, token bị vô hiệu ngay. Đừng cache hay tái sử dụng token; hãy gửi một task mới cho mỗi lần gửi form.


Khắc phục quy trình Turnstile của bạn

Khi tích hợp Turnstile trục trặc, chạy lại năm bước sau theo thứ tự:

  1. Xác minh sitekey — lấy từ data-sitekey hoặc turnstile.render()
  2. Xác minh pageurl — dùng đúng URL, gồm cả giao thức và path
  3. Kiểm tra đường gắn token — trang chờ token ở cf-turnstile-response, g-recaptcha-response hay ở callback?
  4. Luôn dùng json=1 — khi polling kết quả Turnstile
  5. Không dùng lại token — lấy lời giải mới cho mỗi lần gửi

Bắt đầu với bộ giải Turnstile của CaptchaAI, đối chiếu tham số của bạn với tài liệu API, và đọc thêm Cloudflare Turnstile hoạt động thế nào nếu cần hiểu cơ chế của widget.


Bài viết liên quan

Os comentários estão desativados para este artigo.