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.phpbị từ chối (sai key, thiếupageurl, sai tham số). - Giai đoạn polling — vòng lặp đọc kết quả từ
res.phpkhô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à:
- Sai
pageurlchí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 - 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
- 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 | Có | API key CaptchaAI của bạn |
method |
Chuỗi | Có | Phải là turnstile |
sitekey |
Chuỗi | Có | Sitekey của widget Turnstile |
pageurl |
Chuỗi | Có | 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.phptrả 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ố
actiontrong 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=1khi polling. Phản hồi JSON có thể kèmuser_agentcủa bộ giải — một số trang được Cloudflare bảo vệ cần đúnguser_agentnà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 Turnstileg-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:
pageurlgử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 proxy và proxytype 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ự:
- Xác minh sitekey — lấy từ
data-sitekeyhoặcturnstile.render() - Xác minh pageurl — dùng đúng URL, gồm cả giao thức và path
- Kiểm tra đường gắn token — trang chờ token ở
cf-turnstile-response,g-recaptcha-responsehay ở callback? - Luôn dùng
json=1— khi polling kết quả Turnstile - 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.