Токен пришёл от 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()
Что делает этот код:
- Передаёт sitekey и pageurl в
in.phpсmethod=userrecaptcha— точно так же, как для стандартного v2. - Опрашивает
res.phpраз в 5 секунд, пока токен не будет готов. - Ищет имя callback-функции в DOM страницы.
- Вызывает эту функцию с решённым токеном через
execute_script. - Дальше форму отправляет, проверяет или перенаправляет уже сам 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:
- Получите API-ключ — captchaai.com/api.php
- Найдите имя callback-функции — проверьте
data-callback,grecaptcha.render()или внутреннюю конфигурацию. - Возьмите код Python или Node.js выше — подставьте свой ключ, sitekey, URL страницы и имя callback.
- Запустите — токен приходит меньше чем за 60 секунд, срабатывает callback, а дальше страница обрабатывает результат сама.
- Не получилось? Загляните в справочник по распространённым ошибкам reCAPTCHA v2 или откройте полную документацию по API CaptchaAI.
Похожие статьи
Ещё три материала по теме reCAPTCHA и смежным CAPTCHA:
- Решение reCAPTCHA Invisible на Python
- Cloudflare Turnstile и reCAPTCHA v2 на одном сайте
- Recaptcha Enterprise Assessment API: разбор запроса