Khắc Phục Sự Cố

Turnstile trả về 403 dù token hợp lệ: cách khắc phục

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:

  1. 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).
  2. Set-Cookie chứa qa_session_cookie nhưng request sau vẫn 403? Lỗi tái sử dụng session (Nguyên nhân 1).
  3. Không có cookie đó, widget hiện lại? Quá 240 giây là token hết hạn (Nguyên nhân 2).
  4. Vẫn trong ngưỡng nhưng header không khớp? Đồng bộ lại header (Nguyên nhân 4).
  5. 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:

  1. Thiếu cookie phiên (qa_session_cookie) — rất phổ biến
  2. Token đã hết hạn — phổ biến
  3. Gửi token sai endpoint/field — phổ biến
  4. Thiếu header request — trung bình
  5. IP không khớp giữa lúc giải và lúc gửi — trung bình
  6. Nhầm Cloudflare Challenge với Turnstile — dễ gây nhầm lẫn

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-Cookie chứa qa_session_cookie khô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:

  1. Mở DevTools trình duyệt → tab Network
  2. Hoàn thành thử thách Turnstile thủ công
  3. Tìm request submit form
  4. 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 request
  • Referer — phải trỏ đúng URL trang chứa widget
  • Sec-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/Referer khớ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 sau
  • data-sitekey lấ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-Cookie chứa qa_session_cookie sau 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.

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.

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