Phân Tích Kỹ Thuật

3 chế độ widget Cloudflare Turnstile: Managed, Non-interactive, Invisible

Cloudflare Turnstile có ba chế độ widget: managed (Cloudflare tự quyết định mức thử thách), non-interactive (chỉ chạy proof-of-work, không bao giờ hiện giao diện) và invisible (không có container nào xuất hiện trên trang). Nếu script tự động của bạn nhận token cf-turnstile-response lúc thì ngay lập tức, lúc thì phải đợi vài giây với một spinner, nhiều khả năng bạn đang gặp hai chế độ khác nhau chứ không phải một lỗi ngẫu nhiên.

Tin tốt cho việc tích hợp: cả ba chế độ trả về cùng một token, và CaptchaAI giải cả ba bằng đúng một method API — turnstile.


Nhận diện chế độ trong 3 bước

Trước khi đọc chi tiết từng chế độ, đây là cách xác định nhanh site đang chạy gì chỉ bằng cách đọc HTML:

  1. data-size="invisible" (hoặc size: 'invisible' trong JS) → invisible mode, không có container hiển thị.
  2. Không có data-size="invisible" nhưng có data-appearance="interaction-only"non-interactive mode, chỉ hiện spinner, không có checkbox.
  3. Không có cả hai thuộc tính trên → managed mode (mặc định), widget có thể hiện checkbox tùy tín hiệu tin cậy.

Cả ba trường hợp đều dùng chung một class cf-turnstile và trả về cùng một token — bước phân loại này chỉ quyết định bạn cần đợi UI hay không, không đổi cách gọi API giải.

Mẹo debug nhanh: chạy document.querySelectorAll('.cf-turnstile') trong console sau khi trang load xong hẳn — lộ ra container thật kể cả ở invisible mode.


Bảng so sánh nhanh 3 chế độ widget Turnstile

Tiêu chí Managed Non-interactive Invisible
Widget có hiển thị không? Đôi khi (checkbox) Không, chỉ có spinner Không, hoàn toàn ẩn
Cần phần tử container? Có (ẩn)
Cần người dùng tương tác? Đôi khi Không Không
Chạy proof-of-work? Có, có thể leo thang độ khó Có, luôn luôn Có, luôn luôn
Fallback về checkbox khi lỗi? Không, báo lỗi luôn Không, báo lỗi luôn
Token trả về cf-turnstile-response cf-turnstile-response cf-turnstile-response
Method CaptchaAI turnstile turnstile turnstile
Phù hợp với Đăng nhập, đăng ký Form ma sát thấp (newsletter, comment) Xác minh nền, không hiện UI

Chọn chế độ nào phù hợp sản phẩm của bạn

Không có chế độ "tốt nhất" tuyệt đối — chọn theo mức rủi ro chấp nhận đánh đổi:

Ưu tiên Chế độ nên dùng
Bảo mật cao nhất cho đăng nhập, đăng ký Managed
Ma sát tối thiểu cho form giá trị thấp Non-interactive
Không hiện UI cho người dùng Invisible

Hai lưu ý trước khi đổi cấu hình production:

  • Non-interactive không có fallback — trình duyệt cũ có thể bị chặn hẳn thay vì thấy checkbox.
  • Luôn test cả ba chế độ trên staging trước, vì hành vi hiển thị phụ thuộc tín hiệu trình duyệt thực tế.

Managed mode: mặc định của Cloudflare Turnstile

Managed để Cloudflare tự quyết định mức thử thách cho từng khách truy cập.

Phần lớn người dùng vượt qua mà không thấy gì cả. Traffic bị nghi ngờ sẽ thấy checkbox, traffic nghi ngờ cao hơn có thể gặp thử thách phức tạp hơn.

Cách triển khai

<!-- Managed mode (default) -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-theme="light">
</div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Widget phản ứng thế nào theo mức độ tin cậy

Managed điều chỉnh dựa trên tín hiệu của người truy cập:

  • Độ tin cậy cao — pass ẩn, không có UI nào hiện ra.
  • Độ tin cậy trung bình — hiện checkbox, người dùng bấm để xác minh.
  • Độ tin cậy thấp — thử thách tương tác đầy đủ, hoặc bị chặn thẳng.

Với tự động hóa, managed phổ biến nhất nhưng cũng biến thiên nhiều nhất — khó đoán trước chỉ bằng cách đọc HTML tĩnh.

Phát hiện managed mode trong HTML

def is_managed_mode(html):
    """Check if Turnstile is using managed mode (default)."""
    # Managed mode is the default — no explicit mode attribute
    has_turnstile = "cf-turnstile" in html
    has_explicit_mode = 'data-appearance="interaction-only"' in html or \
                        'data-appearance="always"' in html or \
                        'appearance: "interaction-only"' in html
    return has_turnstile and not has_explicit_mode

Non-interactive mode: không checkbox, chỉ có spinner

Non-interactive không bao giờ hiện checkbox hay phần tử tương tác nào.

Nó chạy proof-of-work ở nền, chỉ hiện một spinner đang tải. Nếu thử thách không hoàn thành được mà không cần tương tác, nó báo lỗi luôn thay vì leo thang lên checkbox.

Cách triển khai

<!-- Non-interactive mode -->
<div class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-appearance="interaction-only">
</div>

Hoặc qua JavaScript API:

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

Luồng xử lý

Page loads → Widget initializes
    ↓
Background proof-of-work runs
    ↓
Success → Token generated (no visible UI)
    OR
Failure → Widget reports error (no fallback to checkbox)

Site nào hay chọn non-interactive

Vài trường hợp thường gặp:

  • Form comment và widget phản hồi
  • Đăng ký nhận bản tin (newsletter)
  • Hành động giá trị thấp, cần ma sát tối thiểu
  • Endpoint API có lớp bảo vệ phía trình duyệt

Invisible mode: không widget nào xuất hiện

Invisible đúng nghĩa vô hình — không có phần tử container nào xuất hiện trong viewport.

Widget chạy ngay khi trang tải, hoặc khi bị trigger bằng code, rồi tạo token mà không để lại dấu hiệu trực quan nào.

Cách triển khai

<!-- Invisible mode — container is hidden -->
<div id="turnstile-invisible"
     class="cf-turnstile"
     data-sitekey="0x4AAAAAAAC3DHQhMMQ_Rxrg"
     data-size="invisible">
</div>

Hoặc hoàn toàn bằng JavaScript:

// Programmatic invisible Turnstile
turnstile.render('#hidden-container', {
    sitekey: '0x4AAAAAAAC3DHQhMMQ_Rxrg',
    size: 'invisible',
    callback: function(token) {
        // Token ready — submit form automatically
        submitForm(token);
    },
    'error-callback': function() {
        // Challenge failed
        console.error('Invisible Turnstile failed');
    },
});

Vì sao invisible khó phát hiện hơn

Container không có kích thước hiển thị nên bạn không thể chỉ nhìn DOM để kết luận:

import re

def detect_invisible_turnstile(html):
    """Detect invisible Turnstile on a page."""
    indicators = {
        "script_loaded": "challenges.cloudflare.com/turnstile" in html,
        "size_invisible": 'data-size="invisible"' in html or
                          "size: 'invisible'" in html or
                          'size: "invisible"' in html,
        "api_render_call": "turnstile.render" in html,
        "response_field": "cf-turnstile-response" in html,
    }

    if indicators["script_loaded"] and indicators["size_invisible"]:
        return {"mode": "invisible", "confidence": "high"}
    elif indicators["script_loaded"] and indicators["api_render_call"]:
        return {"mode": "invisible_or_programmatic", "confidence": "medium"}
    elif indicators["response_field"]:
        return {"mode": "turnstile_present", "confidence": "low"}

    return {"mode": "none", "confidence": "high"}

Trích sitekey bất kể site dùng chế độ nào

Dù là managed, non-interactive hay invisible, sitekey luôn là tham số bắt buộc để giải.

Lấy nó từ bất kỳ dạng render nào:

import re

def extract_turnstile_sitekey(html):
    """Extract Turnstile sitekey from page HTML (works for all modes)."""

    # Pattern 1: data-sitekey attribute in HTML
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', html)
    if match:
        return match.group(1)

    # Pattern 2: JavaScript render call
    match = re.search(r"sitekey:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    # Pattern 3: Turnstile config object
    match = re.search(r"siteKey['\"]?\s*[:=]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", html)
    if match:
        return match.group(1)

    return None

Giải cả 3 chế độ Turnstile bằng một API CaptchaAI

Cả ba chế độ Turnstile được CaptchaAI giải giống hệt nhau.

Chế độ không ảnh hưởng đến lệnh gọi API — bạn không cần khai báo trước site đang chạy managed, non-interactive hay invisible.

Tình huống quen thuộc với nhóm QA outsourcing ở TP.HCM: client đổi cấu hình Cloudflare khác nhau giữa dev, staging, production — staging chạy invisible, production chạy managed. Script chỉ cần đúng sitekey và pageurl, không cần code riêng cho từng môi trường.

Ba thứ cần chuẩn bị: sitekey, pageurl, và API key CaptchaAI.

Python

Code chạy được ngay, chỉ cần đổi API_KEY:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_turnstile(sitekey, page_url):
    """Solve any Turnstile mode — managed, non-interactive, or invisible."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_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("Turnstile solve timed out")

# Use with any mode
token = solve_turnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
print(f"Token: {token[:50]}...")

Node.js

Cùng logic, viết lại bằng axios:

const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveTurnstile(sitekey, pageUrl) {
  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "turnstile",
      sitekey,
      pageurl: pageUrl,
      json: 1,
    },
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId, json: 1 },
    });

    if (result.data.status === 1) {
      return result.data.request;
    }
  }

  throw new Error("Turnstile solve timed out");
}

// Same function works for all Turnstile modes
solveTurnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://staging.example.com/qa-login")
  .then((token) => console.log("Token:", token.substring(0, 50)));

Câu hỏi thường gặp về chế độ Turnstile

Turnstile invisible có tốn nhiều thread hơn managed không?

Không. Thread trong bảng giá CaptchaAI đếm theo số task đang giải đồng thời, không phân biệt theo chế độ Turnstile.

Gói BASIC ($15/tháng, 5 thread) cho phép giải tối đa 5 task Turnstile cùng lúc, dù đó là managed, non-interactive hay invisible.

CaptchaAI có giải được cả ba chế độ Turnstile chỉ bằng một API không?

Có. CaptchaAI dùng đúng một method turnstile cho cả ba chế độ.

Sitekey và pageurl là hai tham số bắt buộc duy nhất — bạn không cần khai báo chế độ, định dạng token và luồng xác thực không đổi.

Sitekey có đổi khi site chuyển giữa các chế độ không?

Hiếm khi. Site có thể chuyển từ managed sang non-interactive cho một số trang hoặc phân đoạn người dùng, nhưng sitekey thường giữ nguyên.

Chỉ cách render đổi — luôn phát hiện lại chế độ mỗi lần điều hướng.

Vì sao lấy được token invisible nhưng form vẫn báo lỗi?

Nguyên nhân phổ biến nhất là script trích nhầm sitekey — widget ẩn đôi khi dùng sitekey khác với sitekey xuất hiện trong DevTools lúc debug.

Nguyên nhân thứ hai là token đã hết hạn 300 giây trước khi submit. Dùng hàm trích sitekey ở trên ngay trước khi giải, đừng cache sitekey từ lần crawl trước.

Non-interactive phù hợp với form nào trong sản phẩm thực tế?

Non-interactive hợp với các hành động giá trị thấp mà ma sát phải tối thiểu: form comment, đăng ký nhận bản tin, hoặc endpoint API chỉ cần lớp bảo vệ phía trình duyệt.

Với luồng đăng nhập hoặc đăng ký — nơi Cloudflare cần đánh giá rủi ro sâu hơn — managed vẫn là lựa chọn phổ biến hơn.


Các lỗi thường gặp khi tích hợp Turnstile

Bốn nhóm lỗi chiếm phần lớn ticket hỗ trợ Turnstile:

Hiện tượng Nguyên nhân Cách xử lý
Token hợp lệ nhưng form vẫn từ chối Lấy nhầm sitekey (khác với sitekey đang render trên widget) Kiểm tra sitekey được JavaScript render ra, không chỉ đọc HTML tĩnh
Không tìm thấy widget trong HTML Invisible mode được load sau lần render đầu tiên Đợi trang tải xong hoàn toàn, kiểm tra các response XHR
Nhiều widget Turnstile trên cùng một trang Mỗi form dùng sitekey riêng Khớp đúng sitekey với từng form cụ thể
data-size="compact" gây nhầm khi phát hiện Compact là biến thể kích thước, không phải một chế độ Compact mặc định vẫn chạy managed mode
Có thuộc tính data-action Tag phục vụ phân tích (analytics), không phải chế độ Đưa action vào khi giải nếu site yêu cầu để xác thực
Token hết hạn trước khi submit Token Turnstile hết hạn sau 300 giây Giải ngay trước khi submit form, không cache lâu

Tóm tắt

Ba điều cần nhớ về các chế độ widget Cloudflare Turnstile:

  • Managed, non-interactive, invisible kiểm soát trải nghiệm người dùng, nhưng cả ba luôn trả về cùng một token cf-turnstile-response.
  • Với tự động hóa, cả ba chế độ được bộ giải Turnstile của CaptchaAI xử lý theo đúng một method turnstile — sitekey và pageurl là đủ, tỷ lệ giải thành công cao trên mọi biến thể.
  • Khác biệt thật sự nằm ở khâu phát hiện: managed để lại dấu vết rõ trong HTML tĩnh, còn invisible đòi hỏi phân tích sâu hơn — thường phải đợi JavaScript render xong — mới tìm ra sitekey.

Bài viết liên quan

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