API Tutorials

Как решить обратный вызов reCAPTCHA v2 с помощью API

Токен пришёл от CaptchaAI, вы подставили его в g-recaptcha-response — а форма как будто не заметила решения и продолжает требовать проверку. Причина почти всегда одна: сайт использует callback-реализацию reCAPTCHA v2. Вместо скрытого поля он ждёт, что решённый токен передадут напрямую в JavaScript-функцию, и просто игнорирует поле g-recaptcha-response.

Ниже — как распознать такую реализацию, найти имя нужной функции и вызвать её правильно. Сам запрос к API CaptchaAI при этом не меняется: отличается только то, что вы делаете с токеном после получения.

Ещё не решали reCAPTCHA v2 через API? Сначала разберитестандартный поток решения reCAPTCHA v2, а затем возвращайтесь сюда за вариантом с callback.

Прежде чем читать дальше, убедитесь, что вы на верной странице:

  • токен от CaptchaAI приходит успешно, но страница ведёт себя так, будто CAPTCHA не решена;
  • в исходном коде виджета есть data-callback, либо callback встречается в grecaptcha.render();
  • вы уже пробовали подставлять g-recaptcha-response вручную — и это не сработало.

Если совпало хотя бы два пункта — вам сюда.


Чем callback отличается от стандартного вызова API

Запрос к CaptchaAI не меняется вообще. Разница только в том, куда девается готовый токен.

Шаг Стандарт v2 Callback v2
1. Отправьте заявку на CaptchaAI method=userrecaptcha + ключ сайта + адрес страницы Такой же
2. Опрос на результат action=get + идентификатор капчи Такой же
3. Получить токен Тот же формат токена Такой же
4. Внедрить токен Установить значение поля g-recaptcha-response Вызов функции callback с токеном
5. Отправить форму Триггерная форма отправки Обычно автоматически — этим занимается callback

Важно: не устанавливайте g-recaptcha-response на страницах с callback-реализацией. Поле игнорируется, а функция callback никогда не сработает сама. Если установить только поле и не вызвать функцию, CAPTCHA будет выглядеть нерешённой — хотя токен на руках уже есть.


Как понять, что сайт использует callback

Стандартный reCAPTCHA v2 кладёт решённый токен в скрытое текстовое поле g-recaptcha-response. Callback-реализации это поле игнорируют и вызывают JS-функцию напрямую. Три места, где искать имя функции — по возрастанию надёжности.

1. Атрибут data-callback. Загляните в div виджета reCAPTCHA в исходном коде страницы:

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

Если data-callback существует, сайт использует обратный вызов. Значение (SubmitToken) — это нужное вам имя функции.

2. Вызов grecaptcha.render(). Поищите в JavaScript страницы вызов grecaptcha.render:

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

Свойство callback называет функцию. В данном случае userVerified.

3. Внутренняя конфигурация reCAPTCHA. Если первые два способа ничего не дали, откройте консоль браузера на целевой странице и выполните:

___grecaptcha_cfg.clients[0]

Перейдите по дереву объектов, чтобы найти свойство callback. Точный путь зависит от сайта — это может быть clients[0].aa.l.callback или что-то еще, в зависимости от версии reCAPTCHA и минимизации. Если на странице есть несколько экземпляров reCAPTCHA, проверьте clients[1], clients[2] и т. д.

Запустите в консоли браузера этот скрипт, чтобы найти имена 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-реализация, соберите пять вещей — без них код ниже не запустится:

  • API-ключ CaptchaAI — получите на captchaai.com/api.php. Строка из 32 символов.
  • URL целевой страницы — полный адрес, по которому загружается виджет reCAPTCHA v2.
  • sitekey reCAPTCHA v2 — публичный ключ, привязанный к конкретному виджету.
  • Инструмент браузерной автоматизации — Selenium, Puppeteer или Playwright; вызвать callback без выполнения JavaScript не получится.
  • Имя функции 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

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()

Что делает этот код:

  1. Передаёт sitekey и pageurl в in.php с method=userrecaptcha — точно так же, как для стандартного v2.
  2. Опрашивает res.php раз в 5 секунд, пока токен не будет готов.
  3. Ищет имя callback-функции в DOM страницы.
  4. Вызывает эту функцию с решённым токеном через execute_script.
  5. Дальше форму отправляет, проверяет или перенаправляет уже сам JavaScript сайта — вручную ничего доделывать не нужно.

Node.js и Puppeteer: полный код

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();
})();

Логика та же, что и в Python-версии: solveRecaptchaV2 отправляет запрос и опрашивает результат, detectCallbackName ищет функцию в DOM через page.evaluate, а page.evaluate(name, tkn) в конце вызывает найденную функцию прямо в контексте страницы — без этого шага решённый токен так и останется бесполезной строкой.


PHP: решение на стороне сервера

Запрос к API в PHP ничем не отличается. А вот вызвать callback из чистого PHP не получится — для этого нужен контекст браузера, поэтому пример ниже закрывает только серверную часть (решение токена). Для шага внедрения подключите отдельный инструмент браузерной автоматизации, например php-webdriver.

<?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);
}

Вызов callback из браузера

PHP-скрипт выше отдаёт только сам токен — доставить его до JS-функции обязан отдельный браузерный шаг. Как только токен получен, передайте его в браузер (например, через php-webdriver) и выполните:

SubmitToken("TOKEN_FROM_CAPTCHAAI");

Частые ошибки

# Ошибка Чем это оборачивается Как исправить
1 Устанавливаете g-recaptcha-response вместо вызова callback Страница игнорирует токен — форма не отправляется Найдите имя callback-функции и вызовите её с токеном.
2 Ошиблись в имени callback-функции JS-ошибка: функция не определена Перепроверьте data-callback, grecaptcha.render() или внутреннюю конфигурацию.
3 Callback сработал не на том клиенте Токен ушёл не в тот экземпляр reCAPTCHA на странице с несколькими виджетами Проверьте ___grecaptcha_cfg.clients[1], clients[2] и т. д.
4 Вызываете callback до готовности страницы Функция ещё не определена в контексте страницы Дождитесь DOMContentLoaded или networkidle, прежде чем вызывать.
5 Имя функции обфусцировано или минифицировано Имя в исходном коде искажено до нечитаемого Смотрите живое значение в консоли браузера, а не в статичном исходнике.
6 Путаете callback v2 с невидимой v2 Некоторые невидимые реализации тоже используют callback Проверьте data-size="invisible" — если он есть, см. решение reCAPTCHA Invisible через API

Диагностика типичных проблем

Быстрый разбор по симптому — если вы уже нашли свой случай в таблице выше, начните здесь.

Симптом Причина Что делать
Токен решён, а страница никак не реагирует Вместо вызова callback вы устанавливаете g-recaptcha-response Проверьте, есть ли у виджета data-callback или callback внутри grecaptcha.render(), и вызовите именно эту функцию
ReferenceError: SubmitToken is not defined Функция ещё не загрузилась, либо имя указано неверно Сверьте имя по data-callback/конфигурации, дождитесь полной загрузки страницы, на минифицированных сайтах проверьте window.SubmitToken в консоли
На стандартной версии v2 токен работал, а тут — нет Перед вами callback-реализация, а не стандартная Пройдите ещё раз способы определения из раздела «Как понять, что сайт использует callback» выше и переключитесь на вызов функции
ERROR_BAD_TOKEN_OR_PAGEURL Пара sitekey/pageurl не совпадает с тем, что реально на странице Это ошибка API, к callback отношения не имеет — извлеките оба значения заново
На странице сразу несколько виджетов reCAPTCHA У каждого виджета может быть свой callback Проверьте все div g-recaptcha или переберите ___grecaptcha_cfg.clients целиком и сопоставьте нужный виджет с формой, которую вы автоматизируете
ERROR_CAPTCHA_UNSOLVABLE Задачу не удалось решить Отправьте новый запрос — к callback это не имеет отношения, ошибка встречается и на стандартном потоке

Полный список кодов ошибок — всправочнике по распространённым ошибкам reCAPTCHA v2.


Почему для callback подходит именно CaptchaAI

Фактор Деталь
Тот же вызов API Поток submit/poll идентичен стандартному reCAPTCHA v2 — никаких дополнительных параметров под callback не нужно.
Доля успешных решений Высокая на reCAPTCHA v2 — и для callback, и для стандартной реализации задействован один и тот же решатель.
Скорость решения Обычно укладывается менее чем в 60 секунд.
Совместимость токена Один и тот же токен подходит и для внедрения в g-recaptcha-response, и для вызова callback.
Тарификация Планы по потокам — от BASIC ($15/мес, 5 потоков) с неограниченным числом решений на поток.

Токен от CaptchaAI одинаков независимо от того, как именно сайт реализует reCAPTCHA v2 — стандартно или через callback. Вся разница в вашем клиентском коде: как именно вы доставляете токен обратно на страницу.

Для агентств и фрилансеров, которые ведут QA-автоматизацию сразу для нескольких клиентов, это удобно ещё и с точки зрения бюджета: тарификация по потокам в USD не зависит от того, сколько callback-форм вы тестируете в месяц — 5 потоков тарифа BASIC одинаково стоят и при десяти проверках в день, и при тысяче, что упрощает расчёт стоимости QA-инфраструктуры для проектов с серверами в Европе или Центральной Азии.


Готовый пример проекта на GitHub

Если нужен рабочий проект целиком — с настройкой окружения, опросом, повторными попытками и обработкой ошибок, — смотрите полный пример на GitHub →


Часто задаваемые вопросы

Почему решённый токен reCAPTCHA v2 не отправляет форму?

Чаще всего дело в том, что токен подставлен в g-recaptcha-response, а сайт этого поля не читает — он ждёт вызова JS-функции. Откройте исходный код виджета и проверьте, есть ли data-callback или callback в grecaptcha.render(). Если есть, нужно вызвать именно эту функцию с токеном.

Как найти имя функции callback на странице?

Три места для проверки: атрибут data-callback на div reCAPTCHA, свойство callback внутри вызова grecaptcha.render() в JS-коде страницы, и объект ___grecaptcha_cfg.clients[0] в консоли браузера — там нужное свойство callback может быть спрятано глубже в дереве объекта.

Что делать, если имя callback-функции минифицировано?

Не полагайтесь на статичный исходный код — в собранных бандлах имена функций часто искажены до нечитаемых. Смотрите на живое значение атрибута data-callback в DOM или на актуальное свойство callback в ___grecaptcha_cfg.clients прямо во время выполнения, а не в исходниках сборки.

Как обработать несколько callback-виджетов reCAPTCHA на одной странице?

У каждого экземпляра reCAPTCHA может быть собственная callback-функция. Переберите все элементы .g-recaptcha на странице или пройдите по всему массиву ___grecaptcha_cfg.clients, чтобы найти каждую пару «виджет — callback», и вызывайте нужную функцию именно для той формы, которую автоматизируете.

Можно ли решить callback-reCAPTCHA v2 без браузера?

Сам токен API CaptchaAI отдаёт без браузера — вызов in.php/res.php ничем не отличается от headless-запроса. А вот доставить токен в callback без браузера нельзя: функция существует только в контексте страницы, поэтому для последнего шага нужен Selenium, Puppeteer, Playwright или похожий инструмент.


Порядок действий: от ключа до решённого callback

Собираем всё вместе — пять шагов от пустого API-ключа до сработавшего callback:

  1. Получите API-ключcaptchaai.com/api.php
  2. Найдите имя callback-функции — проверьте data-callback, grecaptcha.render() или внутреннюю конфигурацию.
  3. Возьмите код Python или Node.js выше — подставьте свой ключ, sitekey, URL страницы и имя callback.
  4. Запустите — токен приходит меньше чем за 60 секунд, срабатывает callback, а дальше страница обрабатывает результат сама.
  5. Не получилось? Загляните в справочник по распространённым ошибкам reCAPTCHA v2 или откройте полную документацию по API CaptchaAI.

Похожие статьи

Ещё три материала по теме reCAPTCHA и смежным CAPTCHA:

Комментарии для этой статьи отключены.