Hướng Dẫn API

Cách giải reCAPTCHA v2 dạng callback bằng API

Chèn token vào g-recaptcha-response mà form vẫn không submit? Nhiều khả năng trang bạn đang test dùng reCAPTCHA v2 dạng callback — nó không đọc field ẩn đó, mà đợi bạn gọi thẳng một hàm JavaScript kèm theo token. Lệnh gọi tới API CaptchaAI thì y hệt bản tiêu chuẩn; thứ duy nhất khác là bước cuối, chỗ bạn giao token cho trang.

Bài này đi thẳng vào ba việc: nhận diện trang đang dùng callback, lấy token qua API CaptchaAI, và gọi đúng hàm để trang xử lý tiếp (submit form, xác thực, chuyển hướng…).

Chưa quen reCAPTCHA v2? XemCách giải reCAPTCHA v2 bằng APIcho luồng tiêu chuẩn trước, rồi quay lại đây cho biến thể callback.


Chuẩn bị trước khi bắt đầu

Cần gì Chi tiết
API key CaptchaAI Lấy tạicaptchaai.com/api.php — chuỗi 32 ký tự.
URL trang đích URL đầy đủ nơi widget reCAPTCHA v2 được load.
Sitekey reCAPTCHA v2 Khóa công khai gắn với widget đó.
Công cụ automation trình duyệt Selenium, Puppeteer hoặc Playwright — cần chạy được JavaScript để gọi hàm callback.
Tên hàm callback Hàm JavaScript mà trang mong đợi nhận token.

Hai bối cảnh quen thuộc với đội automation/QA ở Việt Nam hay đụng đúng biến thể callback này:

  • Team outsource QA/automation (kiểu FPT Software, VNG hoặc startup TP.HCM/Hà Nội) kiểm thử login flow trên staging cho khách hàng nước ngoài — widget reCAPTCHA thường được nhúng qua SDK bên thứ ba, không phải cấu hình mặc định của Google, nên hay rơi vào dạng callback.
  • Nhóm scraping nội bộ theo dõi giá trên các sàn thương mại điện tử gắn reCAPTCHA v2 callback ở form đăng nhập/đăng ký để chống bot.

Cả hai đều cần phân biệt đúng callback với chuẩn trước khi viết code, nếu không script sẽ chạy mà không báo lỗi nhưng form vẫn không submit.


Cách nhận diện trang dùng callback

reCAPTCHA v2 tiêu chuẩn ghi token vào textarea ẩn g-recaptcha-response. Bản callback bỏ qua field đó và gọi thẳng một hàm JavaScript.

Ba chỗ cần soát, theo thứ tự nhanh nhất trước:

  • Thuộc tính data-callback trên div widget (nhanh nhất, chỉ cần xem source).
  • Tham số callback trong lệnh gọi grecaptcha.render() (khi widget được tạo bằng JS thay vì HTML tĩnh).
  • Cấu hình nội bộ ___grecaptcha_cfg.clients (dùng khi hai cách trên không thấy gì, ví dụ trang đã minify).
Cách kiểm tra Tốc độ Khi nào cần
data-callback Nhanh nhất — chỉ cần xem source Widget dựng bằng HTML tĩnh
grecaptcha.render() Nhanh — tìm trong file JS Widget dựng động bằng JavaScript
___grecaptcha_cfg.clients Chậm hơn — phải duyệt console Hai cách trên không ra kết quả, hoặc trang đã minify

Cách 1: Kiểm tra thuộc tính data-callback

Kiểm tra div tiện ích reCAPTCHA trong nguồn trang:

<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
     data-callback="SubmitToken">
</div>

Nếu data-callback xuất hiện, trang đang dùng callback. Giá trị (SubmitToken) chính là tên hàm bạn cần.

  • Đây là cách nhanh nhất — không cần chạy JavaScript, chỉ cần "View Page Source".
  • Nếu không thấy thuộc tính này, chuyển sang Cách 2.

Cách 2: Kiểm tra lệnh gọi grecaptcha.render()

Tìm grecaptcha.render trong JavaScript của trang:

grecaptcha.render('recaptcha-container', {
  sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
  callback: userVerified
});

Thuộc tính callback chính là tên hàm. Ở ví dụ này là userVerified.

Cách này áp dụng khi widget được grecaptcha.render() dựng động thay vì khai báo sẵn trong HTML — thường gặp ở single-page app render widget bằng React/Vue.

Cách 3: Đọc cấu hình reCAPTCHA nội bộ

Mở console trình duyệt trên trang đích và chạy:

___grecaptcha_cfg.clients[0]

Duyệt cây đối tượng để tìm thuộc tính callback. Đường dẫn chính xác đổi theo từng trang — có thể là clients[0].aa.l.callback hoặc khác, tùy phiên bản reCAPTCHA và mức độ minify.

Nếu trang có nhiều widget, kiểm tra thêm clients[1], clients[2], v.v. — mỗi widget đăng ký callback riêng trong cùng object clients.

Script phát hiện nhanh

Chạy đoạn này trong console để tự động dò tên hàm callback:

// Check data-callback attributes
document.querySelectorAll('[data-callback]').forEach(el => {
  console.log('data-callback:', el.getAttribute('data-callback'));
});

// Check internal config
if (typeof ___grecaptcha_cfg !== 'undefined') {
  Object.keys(___grecaptcha_cfg.clients).forEach(key => {
    const client = ___grecaptcha_cfg.clients[key];
    console.log(`Client ${key}:`, JSON.stringify(client, null, 2));
  });
}

Callback khác gì reCAPTCHA v2 tiêu chuẩn

Lệnh gọi tới API CaptchaAI giống hệt nhau. Khác biệt duy nhất nằm ở bước cuối — bạn làm gì với token sau khi nhận được.

Bước v2 tiêu chuẩn v2 callback
1. Gửi task tới CaptchaAI method=userrecaptcha + sitekey + pageurl Giống nhau
2. Polling kết quả action=get + captcha ID Giống nhau
3. Nhận token Cùng định dạng token Giống nhau
4. Đưa token vào trang Set giá trị field g-recaptcha-response Gọi hàm callback kèm token
5. Submit form Kích hoạt submit thủ công Thường tự động — callback lo phần này

Lưu ý quan trọng: Đừng set g-recaptcha-response trên trang dùng callback. Trang bỏ qua field đó và chỉ chờ hàm callback được gọi. Set field mà không gọi hàm sẽ khiến trang xử sự như thể CAPTCHA chưa bao giờ được giải.


Vì sao CaptchaAI xử lý tốt trường hợp này

Yếu tố Chi tiết
Cùng một lệnh gọi API Luồng submit/polling giống hệt reCAPTCHA v2 tiêu chuẩn — không cần tham số thêm
Tỷ lệ giải thành công Cao trên reCAPTCHA v2 (callback và tiêu chuẩn dùng chung một solver)
Thời gian giải Thường dưới 60 giây
Token tương thích cả hai kiểu Token trả về dùng được cho cả g-recaptcha-response lẫn gọi callback
Giá Gói theo thread, thấp nhất là BASIC ($15/tháng, 5 thread), không giới hạn số lần giải trong mỗi thread

Token CaptchaAI trả về giống nhau dù trang triển khai reCAPTCHA v2 theo cách nào — điểm mấu chốt cần nhớ trước khi vào code: khác biệt chỉ nằm ở phần client-side, không phải ở cách gọi API.


Sơ đồ luồng xử lý

Toàn bộ quy trình gói gọn trong một vòng submit → poll → invoke, không có bước rẽ nhánh nào riêng cho callback:

Page → extract sitekey + pageurl + callback name
                    ↓
      POST to in.php (method=userrecaptcha)
                    ↓
           receive captcha ID
                    ↓
         wait 15–20 seconds
                    ↓
      GET res.php (action=get, id=…)
          ↓                    ↓
   CAPCHA_NOT_READY       status=1 → token
    (wait 5s, retry)            ↓
                     invoke callback(token)
                              ↓
               site processes token automatically

Code Python (Selenium)

import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By

API_KEY = "YOUR_CAPTCHAAI_API_KEY"
SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
PAGE_URL = "https://staging.example.com/qa-login"
CALLBACK_NAME = "SubmitToken"  # The callback function name from the page

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"

def solve_recaptcha_v2(api_key, sitekey, pageurl):
    """Submit a reCAPTCHA v2 task and return the solved token."""

    # Step 1: Submit the captcha
    submit_resp = requests.post(
        SUBMIT_URL,
        data={
            "key": api_key,
            "method": "userrecaptcha",
            "googlekey": 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}")

    # Step 2: Wait before first poll
    time.sleep(15)

    # Step 3: 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("reCAPTCHA v2 solve timed out")

def detect_callback_name(driver):
    """Detect the reCAPTCHA callback function name from the page."""

    # Try data-callback attribute first
    callback = driver.execute_script("""
        const el = document.querySelector('[data-callback]');
        if (el) return el.getAttribute('data-callback');
        return null;
    """)
    if callback:
        return callback

    # Try internal reCAPTCHA config
    callback = driver.execute_script("""
        if (typeof ___grecaptcha_cfg === 'undefined') return null;
        const clients = ___grecaptcha_cfg.clients;
        for (const key of Object.keys(clients)) {
            const client = clients[key];
            // Walk the object tree to find a callback function
            const json = JSON.stringify(client);
            const match = json.match(/"callback":"(\\w+)"/);
            if (match) return match[1];
        }
        return null;
    """)
    return callback

# Main workflow
driver = webdriver.Chrome()
driver.get(PAGE_URL)

# Detect the callback name (or use the known name)
detected = detect_callback_name(driver)
callback_name = detected or CALLBACK_NAME
print(f"Using callback: {callback_name}")

# Solve the CAPTCHA
token = solve_recaptcha_v2(API_KEY, SITEKEY, PAGE_URL)
print(f"Solved token: {token[:80]}...")

# Invoke the callback with the token
driver.execute_script(f"{callback_name}(arguments[0]);", token)
print("Callback invoked — site should process the token automatically")

# Wait for the page to process
time.sleep(3)
driver.quit()

Code này làm gì:

  1. Gửi sitekey và pageurl tới in.php với method=userrecaptcha — y hệt bản v2 tiêu chuẩn.
  2. Polling res.php mỗi 5 giây cho tới khi có token.
  3. Dò tên hàm callback trực tiếp từ DOM của trang.
  4. Gọi hàm callback đó kèm token vừa nhận, qua execute_script.
  5. JavaScript của chính trang xử lý phần còn lại — submit form, xác thực hoặc chuyển hướng.

Code Node.js (Puppeteer)

Cùng bốn bước như bản Python, chuyển sang async/awaitfetch:

const puppeteer = require("puppeteer");

const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
const PAGE_URL = "https://staging.example.com/qa-login";
const CALLBACK_NAME = "SubmitToken";

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 solveRecaptchaV2(apiKey, sitekey, pageurl) {
  // Step 1: Submit the captcha
  const submitResp = await fetch(SUBMIT_URL, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      key: apiKey,
      method: "userrecaptcha",
      googlekey: 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}`);

  // Step 2: Wait before first poll
  await sleep(15_000);

  // Step 3: 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("reCAPTCHA v2 solve timed out");
}

async function detectCallbackName(page) {
  return page.evaluate(() => {
    // Try data-callback attribute
    const el = document.querySelector("[data-callback]");
    if (el) return el.getAttribute("data-callback");

    // Try internal config
    if (typeof ___grecaptcha_cfg !== "undefined") {
      const clients = ___grecaptcha_cfg.clients;
      for (const key of Object.keys(clients)) {
        const json = JSON.stringify(clients[key]);
        const match = json.match(/"callback":"(\w+)"/);
        if (match) return match[1];
      }
    }

    return null;
  });
}

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto(PAGE_URL, { waitUntil: "networkidle2" });

  // Detect callback
  const detected = await detectCallbackName(page);
  const callbackName = detected || CALLBACK_NAME;
  console.log(`Using callback: ${callbackName}`);

  // Solve the CAPTCHA
  const token = await solveRecaptchaV2(API_KEY, SITEKEY, PAGE_URL);
  console.log(`Solved token: ${token.slice(0, 80)}...`);

  // Invoke the callback
  await page.evaluate(
    (name, tkn) => {
      window[name](tkn);
    },
    callbackName,
    token
  );
  console.log("Callback invoked — site should process the token automatically");

  await sleep(3_000);
  await browser.close();
})();

So với bản Python, logic giống hệt — chỉ khác cú pháp:

  • fetch thay cho requests, await sleep() thay cho time.sleep().
  • page.evaluate() thay cho driver.execute_script() để dò callback và gọi hàm.
  • Puppeteer chạy headless: false trong ví dụ này để dễ quan sát khi debug; đổi thành true cho môi trường CI/staging.

Code PHP

Lệnh gọi API trong PHP giống hệt hai ngôn ngữ trên, nhưng có một giới hạn cần lưu ý trước khi dùng.

Gọi callback cần ngữ cảnh trình duyệt (DOM + JS runtime), nên ví dụ dưới đây chỉ xử lý phần lấy token phía server. Dùng một công cụ headless (ví dụ: PHP WebDriver) cho bước gọi hàm.

<?php
$apiKey  = "YOUR_CAPTCHAAI_API_KEY";
$sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
$pageurl = "https://staging.example.com/qa-login";

// Step 1: Submit
$submit = file_get_contents("https://ocr.captchaai.com/in.php?" . http_build_query([
    "key"       => $apiKey,
    "method"    => "userrecaptcha",
    "googlekey" => $sitekey,
    "pageurl"   => $pageurl,
    "json"      => 1,
]));

$submitData = json_decode($submit, true);
if ($submitData["status"] !== 1) {
    die("Submit failed: " . $submit);
}

$captchaId = $submitData["request"];
echo "Task created — captcha ID: $captchaId\n";

// Step 2: Wait and poll
sleep(15);

for ($i = 0; $i < 60; $i++) {
    $result = file_get_contents("https://ocr.captchaai.com/res.php?" . http_build_query([
        "key"    => $apiKey,
        "action" => "get",
        "id"     => $captchaId,
        "json"   => 1,
    ]));

    $resultData = json_decode($result, true);

    if ($resultData["request"] === "CAPCHA_NOT_READY") {
        sleep(5);
        continue;
    }

    if ($resultData["status"] === 1) {
        $token = $resultData["request"];
        echo "Solved token: " . substr($token, 0, 80) . "...\n";
        // Pass $token to your browser automation to invoke the callback
        break;
    }

    die("Polling error: " . $result);
}

Có token từ PHP rồi, dùng công cụ automation trình duyệt (ví dụ: php-webdriver) để chạy:

SubmitToken("TOKEN_FROM_CAPTCHAAI");

Debug các tình huống thực tế

Trước khi tra bảng lỗi nhanh, đây là cách xử lý từng tình huống cụ thể:

Có token nhưng trang không phản ứng gì

Nguyên nhân phổ biến nhất: bạn set g-recaptcha-response thay vì gọi callback.

Kiểm tra hai chỗ, theo đúng thứ tự đã nêu ở phần nhận diện phía trên:

  • Widget có data-callback không?
  • grecaptcha.render() có tham số callback không?

Nếu có, bạn bắt buộc phải gọi đúng hàm đó — set field sẽ bị bỏ qua hoàn toàn.

ReferenceError: SubmitToken is not defined

Hàm callback chưa được load hoặc bạn đang dùng sai tên. Thử theo thứ tự:

  1. Xác nhận lại tên qua data-callback hoặc cấu hình nội bộ.
  2. Đợi trang load xong hẳn rồi mới gọi.
  3. Trên site đã minify, hàm có thể được gán cho một biến khác — kiểm tra window.SubmitToken trong console.

Token chạy tốt trên v2 tiêu chuẩn nhưng fail ở trang này

Nhiều khả năng đây là bản callback. Làm lạicác bước nhận diệnở trên để xác nhận, rồi chuyển sang gọi callback thay vì set field.

Trang có nhiều widget reCAPTCHA cùng lúc

Mỗi widget có thể gắn callback riêng. Soát từng div g-recaptcha, hoặc duyệt ___grecaptcha_cfg.clients để liệt kê hết các instance đã đăng ký, rồi khớp đúng widget với form bạn đang target.

Hai lỗi API không đặc thù cho callback

Hai mã lỗi này xuất hiện y hệt nhau trên cả reCAPTCHA v2 tiêu chuẩn lẫn callback — không liên quan đến cách bạn đưa token vào trang:

Mã lỗi Nguyên nhân Cách xử lý
ERROR_BAD_TOKEN_OR_PAGEURL Cặp sitekey/pageurl sai Trích xuất lại cả hai giá trị từ trang
ERROR_CAPTCHA_UNSOLVABLE Challenge không giải được Thử lại với request mới

Lỗi hay gặp khi xử lý callback

Bảng tra nhanh sáu lỗi phổ biến nhất, kèm cách sửa:

# Lỗi Hậu quả Cách sửa
1 Set g-recaptcha-response thay vì gọi callback Trang bỏ qua token — form không bao giờ submit Tìm tên hàm callback và gọi nó kèm token
2 Sai tên hàm callback JavaScript báo lỗi: hàm không tồn tại Soát lại data-callback, grecaptcha.render() hoặc cấu hình nội bộ
3 Callback nằm ở client index khác Nhắm nhầm widget trên trang có nhiều reCAPTCHA Kiểm tra thêm ___grecaptcha_cfg.clients[1], clients[2], v.v.
4 Gọi callback trước khi trang sẵn sàng Hàm chưa tồn tại trong context của trang Đợi DOMContentLoaded hoặc networkidle rồi mới gọi
5 Dùng tên hàm đã bị minify sai Tên trong source code không khớp thực tế Dò tham chiếu hàm thật bằng console lúc trang đang chạy
6 Nhầm callback v2 với v2 invisible Một số bản invisible cũng dùng callback Kiểm tra data-size="invisible" — nếu có, xemCách giải reCAPTCHA Invisible bằng API

Nếu form vẫn không submit sau khi đã giải token, kiểm tra theo đúng thứ tự này trước khi đào sâu hơn:

  1. Bạn đang gọi hàm callback hay đang set field g-recaptcha-response? Đây là nguyên nhân số 1.
  2. Tên hàm bạn gọi có khớp chính xác với data-callback/grecaptcha.render() không?
  3. Bạn gọi hàm đúng lúc trang đã sẵn sàng (DOMContentLoaded/networkidle) chưa?

Xem đầy đủ danh sách lỗi tạiCác lỗi giải reCAPTCHA v2 thường gặp.


Ví dụ chạy được đầy đủ

Cần một project hoàn chỉnh với setup môi trường, polling, retry và xử lý lỗi sẵn?

Xem ví dụ đầy đủ trên GitHub →


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

Năm câu hỏi đội automation/QA hay hỏi nhất về biến thể callback:

Callback reCAPTCHA v2 có làm khác lệnh gọi API CaptchaAI không?

Không khác chút nào. Vẫn cùng một request method=userrecaptcha với sitekey và pageurl y hệt.

Khác biệt duy nhất nằm ở bước cuối — bạn gọi hàm callback thay vì set giá trị field.

Vì sao đã nhận được token mà trang không phản ứng gì?

Nguyên nhân phổ biến nhất là bạn set g-recaptcha-response trong khi trang lại đợi callback được gọi.

Kiểm tra data-callback hoặc thuộc tính callback trong grecaptcha.render() — nếu tồn tại, bạn phải gọi đúng hàm đó, không phải set field.

reCAPTCHA v2 dạng callback có tính phí khác reCAPTCHA v2 chuẩn không?

Không — cùng một gói giá:

  • CaptchaAI tính phí theo thread, không theo loại reCAPTCHA hay cách trang chèn token.
  • Gói BASIC ($15/tháng, 5 thread) trở lên xử lý cả hai dạng, không phụ phí riêng cho callback.

Có bắt buộc dùng Selenium/Puppeteer để gọi callback không?

Có. Callback là một hàm JavaScript sống trong context của trang, nên bạn cần một công cụ chạy được JavaScript trên trang đích.

Selenium, Puppeteer, Playwright hoặc tương đương đều dùng được. Giải token thuần qua API (như ví dụ PHP phía trên) không đủ để tự gọi hàm — bạn vẫn cần một browser context để invoke nó.

reCAPTCHA v2 callback và reCAPTCHA v2 invisible có luôn đi cùng nhau không?

Không bắt buộc, nhưng khá phổ biến — nhiều bản invisible cũng dùng callback để nhận token. Kiểm tra data-size="invisible" trên widget; nếu có, tham khảo thêmCách giải reCAPTCHA Invisible bằng API vì luồng phát hiện hơi khác.


Bắt đầu giải reCAPTCHA v2 callback

Tóm tắt toàn bộ quy trình thành năm bước copy-paste được ngay:

  1. Lấy API keycaptchaai.com/api.php
  2. Xác định tên hàm callback — kiểm tra data-callback, grecaptcha.render() hoặc cấu hình nội bộ
  3. Copy code Python hoặc Node.js ở trên — thay các placeholder bằng key, sitekey, pageurl và tên callback thực tế
  4. Chạy thử — token về sau chưa đầy 60 giây, callback được gọi, trang tự xử lý phần còn lại
  5. Vẫn vướng? XemCác lỗi giải reCAPTCHA v2 thường gặphoặc đọc trọnTài liệu API CaptchaAI

Bài viết liên quan

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