Bạn gửi đúng token Turnstile nhưng server vẫn trả 403 Forbidden? Thủ phạm gần như luôn là cookie phiên bị mất giữa bước giải và bước gửi, không phải bản thân token. Dưới đây là toàn bộ nguyên nhân, xếp theo mức độ phổ biến, kèm cách kiểm tra và sửa từng trường hợp.
Cách xác định nhanh bạn đang gặp lỗi nào
Trước khi đọc hết 5 nguyên nhân, chạy qua thứ tự kiểm tra sau — hầu hết lộ diện ngay ở bước hai hoặc ba:
- Không có header
Server: cloudflare? Lỗi tầng ứng dụng — kiểm tra tên field gửi token (Nguyên nhân 3). - Có
Set-Cookiechứaqa_session_cookienhưng request sau vẫn 403? Lỗi tái sử dụng session (Nguyên nhân 1). - Không có cookie đó, widget hiện lại? Quá 240 giây là token hết hạn (Nguyên nhân 2).
- Vẫn trong ngưỡng nhưng header không khớp? Đồng bộ lại header (Nguyên nhân 4).
- Cả trang là màn hình thử thách, không phải checkbox nhỏ? Đó là Cloudflare Challenge (Nguyên nhân 5).
Mẹo: so sánh header + cookie của lần gọi thành công gần nhất với lần bị 403 — thường nhanh hơn dò từng nguyên nhân.
Vì sao 403 xuất hiện dù token hợp lệ
Xếp theo tần suất gặp trong pipeline QA/scraping:
- Thiếu cookie phiên (
qa_session_cookie) — rất phổ biến - Token đã hết hạn — phổ biến
- Gửi token sai endpoint/field — phổ biến
- Thiếu header request — trung bình
- IP không khớp giữa lúc giải và lúc gửi — trung bình
- Nhầm Cloudflare Challenge với Turnstile — dễ gây nhầm lẫn
Nguyên nhân 1: thiếu cookie phiên (qa_session_cookie)
Turnstile đặt cookie khi xác thực. Request tiếp theo không mang cookie này thì Cloudflare chặn, dù token hoàn toàn hợp lệ.
import requests
session = requests.Session()
# Step 1: Load the page to get initial cookies
session.get("https://example.com")
# Step 2: Solve Turnstile
token = solve_turnstile(
api_key="YOUR_API_KEY",
sitekey="TURNSTILE_SITEKEY",
pageurl="https://example.com",
)
# Step 3: Submit token to the validation endpoint
# This sets qa_session_cookie cookie
resp = session.post("https://example.com/api/verify", data={
"cf-turnstile-response": token,
}, headers={
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "https://example.com",
"Referer": "https://example.com/",
})
# Step 4: Now make your actual request WITH the session cookies
resp = session.get("https://example.com/protected-page")
print(resp.status_code) # Should be 200 now
Xác nhận hai điểm sau khi chạy đoạn trên:
- Response Step 3 có
Set-Cookiechứaqa_session_cookiekhông session(không phải request rời) được truyền tiếp cho mọi lệnh gọi sau
Nguyên nhân 2: token đã hết hạn
Token Turnstile có hiệu lực khoảng 300 giây, nhưng nên dùng ngay sau khi giải xong để tránh rủi ro.
import time
# Solve
start = time.time()
token = solve_turnstile(...)
solve_time = time.time() - start
# Check if token is still fresh
if solve_time > 240: # > 4 minutes is risky
print("Token may be too old, solving again...")
token = solve_turnstile(...)
# Submit immediately
submit_token(token)
Ngưỡng 240 giây là mốc an toàn để chủ động giải lại — token vẫn còn hiệu lực kỹ thuật tới ~300 giây, nhưng đừng canh sát mốc tối đa.
Nguyên nhân 3: gửi token sai field hoặc sai phương thức
Mỗi trang nhận token theo cách riêng — field ẩn, header tùy chỉnh, hoặc JSON body:
# Some sites use a hidden form field
data = {
"cf-turnstile-response": token,
"username": "user",
"password": "pass",
}
# Some sites use a custom header
headers = {
"X-Turnstile-Token": token,
}
# Some sites use JSON body
json_data = {
"turnstileToken": token,
"email": "[email protected]",
}
Cách tìm đúng tên field:
- Mở DevTools trình duyệt → tab Network
- Hoàn thành thử thách Turnstile thủ công
- Tìm request submit form
- Xem request body để biết tên field chứa token
Nguyên nhân 4: thiếu header request
Cloudflare kiểm tra tính nhất quán của các header trong mỗi request:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Origin": "https://example.com",
"Referer": "https://staging.example.com/qa-login",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
}
session.headers.update(headers)
Ba header hay bị bỏ sót nhất:
Origin— nhiều site so khớp chặt với domain gửi requestReferer— phải trỏ đúng URL trang chứa widgetSec-Fetch-*— trình duyệt thật luôn gửi, script tự viết hay quên
Nguyên nhân 5: nhầm giữa Turnstile và Cloudflare Challenge
Turnstile và Cloudflare Challenge dùng chung nhãn "Cloudflare" nên dễ nhầm, dù là hai cơ chế khác nhau:
| Tính năng | Turnstile | Cloudflare Challenge |
|---|---|---|
| Widget | Checkbox hiển thị trên trang | Màn hình thử thách toàn trang |
| Method CaptchaAI | turnstile |
cloudflare_challenge |
| Trường token | cf-turnstile-response |
N/A (dựa trên cookie) |
Thấy màn hình thử thách toàn trang thay vì checkbox, chuyển sang method=cloudflare_challenge.
Ví dụ đầy đủ: giải Turnstile và truy cập trang được bảo vệ
Đoạn code dưới đây gộp toàn bộ luồng — lấy sitekey, giải token, truy cập trang bằng cùng một session:
import requests
import time
import re
def solve_turnstile_and_access(target_url, api_key):
"""Complete flow: solve Turnstile and access protected page."""
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
})
# Load page, get cookies and sitekey
resp = session.get(target_url)
match = re.search(r'data-sitekey="([^"]+)"', resp.text)
if not match:
raise RuntimeError("Turnstile sitekey not found")
sitekey = match.group(1)
# Solve via CaptchaAI
submit_resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": target_url,
"json": 1,
}, timeout=30)
task_id = submit_resp.json()["request"]
# Poll
for _ in range(12):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = poll.json()
if data.get("status") == 1:
token = data["request"]
break
else:
raise TimeoutError("Solve timeout")
# Submit token using the same session
form_resp = session.post(target_url, data={
"cf-turnstile-response": token,
}, headers={
"Origin": f"https://{requests.utils.urlparse(target_url).netloc}",
"Referer": target_url,
})
return session, form_resp
# Usage
session, resp = solve_turnstile_and_access(
"https://staging.example.com/qa-login",
"YOUR_API_KEY",
)
# session now has valid cookies for subsequent requests
Tình huống thực tế: đội QA outsource xử lý lỗi 403 ngắt quãng
Một đội QA thuê ngoài tại TP.HCM chạy pipeline kiểm thử đăng nhập: Turnstile giải thành công, request đầu trả 200 OK, nhưng request thứ hai vài phút sau lại nhận 403. Cả đội nghi token hết hạn, giải lại nhiều lần — lỗi vẫn lặp ở bước hai.
Nguyên nhân: helper function tạo requests.Session() mới cho mỗi lần gọi thay vì tái sử dụng session đã có qa_session_cookie.
Bài học:
- Khởi tạo
Session()một lần ở tầng gọi ngoài cùng, truyền xuyên suốt giải → gửi → các bước sau - Token dùng một lần, nhưng cookie phiên phải sống suốt pipeline
Bảng khắc phục nhanh
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| 403 dù token hợp lệ | Thiếu cookie phiên | Dùng cùng một session cho mọi request |
| 403 ở các trang tiếp theo | qa_session_cookie chưa được set |
Bước xác thực token phải trả về cookie |
| Chạy được một lần rồi 403 | Cookie phiên đã hết hạn | Giải lại để lấy cookie mới |
| Luôn luôn 403 | Đây là Cloudflare Challenge, không phải Turnstile | Dùng method cloudflare_challenge |
Checklist trước khi báo lỗi cho solver
Rà qua danh sách này trước — phần lớn báo lỗi 403 dừng lại ngay đây:
- User-Agent giống nhau giữa giải và gửi
- IP đầu ra giống nhau (proxy cố định cấp session nếu có dùng)
Origin/Refererkhớp URL trang đích- Token gửi trong 240 giây, chỉ gửi một lần
- Tên field đã xác nhận trong DevTools
requests.Session()tái sử dụng xuyên suốt giải → gửi → request saudata-sitekeylấy lại mỗi lần chạy, không hardcode- Đã xác nhận đây là Turnstile, không phải Cloudflare Challenge
- Server trả
Set-Cookiechứaqa_session_cookiesau khi gửi token
Câu hỏi thường gặp
Vì sao Turnstile trả về 403 dù đã gửi đúng token?
Thường không phải do token, mà do cookie phiên. Nếu request kế tiếp không dùng cùng session với bước xác thực token, Cloudflare xem đó là client mới.
qa_session_cookie tồn tại bao lâu?
Thường 30 phút đến 24 giờ tùy cấu hình trang. Khi request bắt đầu thất bại trở lại, giải lại Turnstile để lấy cookie mới.
Cloudflare Challenge và Turnstile khác nhau thế nào khi debug 403?
Turnstile hiển thị checkbox và trả token qua field cf-turnstile-response. Cloudflare Challenge là màn hình thử thách toàn trang, không có field token — xác thực dựa trên cookie. Thấy toàn trang bị chặn thì đổi method sang cloudflare_challenge.
Vì sao 403 chỉ xảy ra ở request thứ hai, không phải request đầu?
Thường do helper function tạo Session() mới cho mỗi lệnh gọi thay vì tái sử dụng — request đầu vẫn còn trong luồng có cookie, request sau đã mất. Kiểm tra xem session có được truyền xuyên suốt hàm gọi hay không.
Hướng dẫn liên quan
Sửa lỗi 403 nhanh hơn — giải Turnstile với CaptchaAI.