Some websites implement reCAPTCHA v2 with a callback function instead of the standard g-recaptcha-response hidden field. When you solve the CAPTCHA and inject the token into the hidden field, nothing happens — the page ignores it. That is because the site expects you to call a JavaScript function with the token instead.
This guide shows you how to detect callback-based reCAPTCHA v2, solve it through the CaptchaAI API, and invoke the callback correctly. The API call is identical to standard reCAPTCHA v2 — only the token injection step changes.
New to reCAPTCHA v2? Start with How to Solve reCAPTCHA v2 Using API for the standard flow, then come back here for the callback variant.
What you need before you start
| Requirement | Details |
|---|---|
| CaptchaAI API key | Get one from captchaai.com/api.php. 32-character string. |
| Target page URL | The full URL where the reCAPTCHA v2 widget loads. |
| reCAPTCHA v2 sitekey | The public key tied to the widget instance. |
| Browser automation tool | Selenium, Puppeteer, or Playwright — you need JavaScript execution to invoke the callback. |
| Callback function name | The JavaScript function the site expects to receive the token. |
How to identify a callback implementation
Standard reCAPTCHA v2 writes the solved token into a hidden g-recaptcha-response textarea. Callback implementations skip that and call a JavaScript function directly. Here is how to tell the difference.
Method 1: Check the data-callback attribute
Inspect the reCAPTCHA widget div in the page source:
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-callback="SubmitToken">
</div>
If data-callback exists, the site uses a callback. The value (SubmitToken) is the function name you need.
Method 2: Check grecaptcha.render() calls
Search the page's JavaScript for grecaptcha.render:
grecaptcha.render('recaptcha-container', {
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
callback: userVerified
});
The callback property names the function. In this case, userVerified.
Method 3: Inspect the internal reCAPTCHA config
Open the browser console on the target page and run:
___grecaptcha_cfg.clients[0]
Navigate the object tree to find the callback property. The exact path varies by site — it might be clients[0].aa.l.callback or something else depending on the reCAPTCHA version and minification. If the page has multiple reCAPTCHA instances, check clients[1], clients[2], etc.
Quick detection script
Run this in the browser console to find callback names automatically:
// 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));
});
}
How callback solving differs from standard v2
The API call to CaptchaAI is identical. The only difference is what you do with the token after you receive it.
| Step | Standard v2 | Callback v2 |
|---|---|---|
| 1. Submit to CaptchaAI | method=userrecaptcha + sitekey + pageurl |
Same |
| 2. Poll for result | action=get + captcha ID |
Same |
| 3. Receive token | Same token format | Same |
| 4. Inject token | Set g-recaptcha-response field value |
Call the callback function with the token |
| 5. Submit form | Trigger form submit | Usually automatic — the callback handles it |
Critical: Do not set
g-recaptcha-responseon callback-based implementations. The page ignores that field and waits for the callback function to fire. Setting the field without calling the callback will make it look like the CAPTCHA was never solved.
Solving flow
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 implementation (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://example.com/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()
What this does:
- Submits the sitekey and pageurl to
in.phpwithmethod=userrecaptcha— identical to standard v2. - Polls
res.phpevery 5 seconds until the token is ready. - Detects the callback function name from the page DOM.
- Calls the callback function with the solved token using
execute_script. - The site's own JavaScript handles the rest — form submission, validation, or page redirect.
Node.js implementation (Puppeteer)
const puppeteer = require("puppeteer");
const API_KEY = "YOUR_CAPTCHAAI_API_KEY";
const SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
const PAGE_URL = "https://example.com/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();
})();
PHP implementation
The API call is the same in PHP. Callback invocation requires a browser context, so this example covers the server-side solve. Use a headless browser tool (e.g., PHP WebDriver) for the injection step.
<?php
$apiKey = "YOUR_CAPTCHAAI_API_KEY";
$sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-";
$pageurl = "https://example.com/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);
}
After getting the token in PHP, use a browser automation tool (e.g., php-webdriver) to execute:
SubmitToken("TOKEN_FROM_CAPTCHAAI");
Common mistakes
| # | Mistake | What happens | Fix |
|---|---|---|---|
| 1 | Setting g-recaptcha-response instead of calling the callback |
Page ignores the token — form never submits | Find the callback name and invoke it with the token |
| 2 | Wrong callback function name | JavaScript error: function not defined | Re-check data-callback, grecaptcha.render(), or internal config |
| 3 | Callback is on a different client index | Wrong reCAPTCHA instance targeted on multi-widget pages | Check ___grecaptcha_cfg.clients[1], clients[2], etc. |
| 4 | Calling the callback before the page is ready | Function not yet defined in the page context | Wait for DOMContentLoaded or networkidle before invoking |
| 5 | Using an obfuscated/minified name | The callback name in source is mangled | Use the runtime browser console to find the actual function reference |
| 6 | Mixing callback v2 with invisible v2 | Some invisible implementations also use callbacks | Check if data-size="invisible" is present — if so, see How to Solve reCAPTCHA Invisible Using API |
Troubleshooting
Token solved but page does not react
The most common cause: you set g-recaptcha-response instead of calling the callback. Check whether the widget has data-callback or callback in grecaptcha.render(). If it does, you must invoke that function.
ReferenceError: SubmitToken is not defined
The callback function is not yet loaded or the name is wrong. Try:
- Confirm the name by inspecting
data-callbackor the internal config. - Wait for the page to fully load before invoking.
- On minified sites, the function may be assigned to a variable — check
window.SubmitTokenin the console.
Token works on standard v2 but fails on this page
You are probably facing a callback implementation. Follow the detection steps above to confirm, then switch to callback invocation.
ERROR_BAD_TOKEN_OR_PAGEURL
The sitekey/pageurl pair is invalid. This is an API error, not related to callback vs standard. Re-extract both values from the page.
Page has multiple reCAPTCHA widgets
Each widget may have its own callback. Inspect each g-recaptcha div or check ___grecaptcha_cfg.clients for all registered instances. Match the widget to the form you are targeting.
ERROR_CAPTCHA_UNSOLVABLE
The challenge could not be solved. Retry with a fresh request. This is not callback-specific.
For the complete error reference, see Common reCAPTCHA v2 Solving Errors.
Why CaptchaAI works for this
| Factor | Detail |
|---|---|
| Same API call | The submit/poll flow is identical to standard reCAPTCHA v2 — no extra parameters needed |
| Success rate | 99.5%+ for reCAPTCHA v2 (callback and standard use the same solver) |
| Solve speed | Under 60 seconds |
| Token compatibility | The returned token works with both g-recaptcha-response injection and callback invocation |
| Pricing | Thread-based plans starting at $15/month for unlimited solves |
The token CaptchaAI returns is the same regardless of how the site implements reCAPTCHA v2. The difference is entirely in your client-side code — how you deliver the token to the page.
FAQ
What is a reCAPTCHA v2 callback?
A callback is a JavaScript function that a website registers to receive the solved reCAPTCHA token. Instead of writing the token to the hidden g-recaptcha-response field, reCAPTCHA calls the function directly. The function typically triggers form submission, validation, or a page redirect.
How is callback different from standard reCAPTCHA v2 for the API call?
It is not different at all. You send the same method=userrecaptcha request with the same sitekey and pageurl. The only difference is what you do with the token after receiving it — you call the callback function instead of setting a field value.
How do I find the callback function name?
Three places to check: (1) data-callback attribute on the reCAPTCHA div, (2) callback property in a grecaptcha.render() call in the page JavaScript, (3) ___grecaptcha_cfg.clients[0] object in the browser console — navigate the tree to find the callback property.
Can I use the token with both methods?
The token itself works either way. But if the site expects a callback, setting g-recaptcha-response will be ignored. Always match the injection method to what the site expects.
Do I need a browser to invoke the callback?
Yes. The callback is a JavaScript function in the page context. You need Selenium, Puppeteer, Playwright, or a similar tool that can run JavaScript on the target page.
Start solving reCAPTCHA v2 callback
- Get your API key — captchaai.com/api.php
- Detect the callback name — check
data-callback,grecaptcha.render(), or the internal config - Copy the Python or Node.js code above — replace placeholders with your key, sitekey, pageurl, and callback name
- Run it — the token arrives in under 60 seconds, the callback fires, and the page processes the result
- Stuck? Start with Common reCAPTCHA v2 Solving Errors or read the full CaptchaAI API docs
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.