Quand un reCAPTCHA v2 repose sur un callback, injecter le token dans le champ g-recaptcha-response ne suffit pas : la page ignore ce champ et attend que vous appeliez une fonction JavaScript précise. C'est exactement pour cela qu'un solve « réussi » peut sembler ne rien déclencher — le token est valide, mais il n'arrive jamais là où le site l'attend.
Ce guide montre comment reconnaître une implémentation à callback, la résoudre via l'API CaptchaAI et appeler la bonne fonction avec le token. Bonne nouvelle : l'appel à l'API est strictement le même que pour un reCAPTCHA v2 classique. Seule l'étape d'injection change.
Vous débutez avec reCAPTCHA v2 ? Commencez par le guide du flux standard, puis revenez ici pour la variante à callback.
Le callback en bref : ce qui change par rapport au v2 standard
L'appel à CaptchaAI est identique. La seule différence tient à ce que vous faites du token une fois reçu.
| Étape | reCAPTCHA v2 standard | reCAPTCHA v2 à callback |
|---|---|---|
| 1. Envoi à CaptchaAI | method=userrecaptcha + sitekey + pageurl |
Identique |
| 2. Interrogation du résultat | action=get + identifiant du captcha |
Identique |
| 3. Réception du token | Même format de token | Identique |
| 4. Injection du token | Renseignez le champ g-recaptcha-response |
Appelez la fonction de callback avec le token |
| 5. Envoi du formulaire | Déclenchez la soumission | En général automatique : le callback s'en charge |
À retenir : ne renseignez jamais
g-recaptcha-responsesur une implémentation à callback. La page ignore ce champ et attend le déclenchement de la fonction. Remplir le champ sans appeler le callback donne l'impression que le CAPTCHA n'a jamais été résolu.
Ce qu'il vous faut avant de commencer
Cinq éléments suffisent pour dérouler l'exemple de bout en bout :
- Clé API CaptchaAI — à récupérer sur captchaai.com/api.php, sous la forme d'une chaîne de 32 caractères.
- URL de la page cible — l'URL complète où se charge le widget reCAPTCHA v2.
- Sitekey reCAPTCHA v2 — la clé publique liée à l'instance du widget.
- Outil d'automatisation du navigateur — Selenium, Puppeteer ou Playwright ; l'exécution de JavaScript est indispensable pour appeler le callback.
- Nom de la fonction de callback — la fonction JavaScript que le site attend pour recevoir le token.
Détecter un callback reCAPTCHA v2
Le reCAPTCHA v2 standard écrit le token résolu dans une zone de texte cachée g-recaptcha-response. Une implémentation à callback saute cette étape et appelle directement une fonction JavaScript. Trois indices permettent de reconnaître ce comportement.
Méthode 1 : l'attribut data-callback
Inspectez le <div> du widget reCAPTCHA dans le code source de la page. Si l'attribut data-callback y figure, le site utilise un callback, et sa valeur (ici SubmitToken) est le nom exact de la fonction à appeler.
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
data-callback="SubmitToken">
</div>
Méthode 2 : les appels à grecaptcha.render()
Cherchez grecaptcha.render dans le JavaScript de la page ; la propriété callback y désigne la fonction attendue — ici, userVerified.
grecaptcha.render('recaptcha-container', {
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
callback: userVerified
});
Méthode 3 : la configuration interne de reCAPTCHA
Ouvrez la console du navigateur sur la page cible, exécutez la commande ci-dessous, puis parcourez l'arborescence de l'objet pour trouver la propriété callback. Le chemin exact dépend du site : il peut s'agir de clients[0].aa.l.callback ou d'autre chose selon la version de reCAPTCHA et le niveau de minification. Si la page contient plusieurs instances, testez clients[1], clients[2], etc.
___grecaptcha_cfg.clients[0]
Script de détection automatique des callbacks
Plutôt que d'inspecter chaque widget à la main, collez ce script dans la console du navigateur : il liste automatiquement les noms de callback exposés par la page.
// 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));
});
}
Le flux de résolution, étape par étape
Voici l'enchaînement complet, du chargement de la page au traitement automatique du token par le site :
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
Implémentation 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://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()
Ce que fait ce script :
- Envoie le sitekey et l'URL de page à
in.phpavecmethod=userrecaptcha— identique au v2 standard. - Interroge
res.phptoutes les 5 secondes jusqu'à ce que le token soit prêt. - Détecte le nom de la fonction de callback à partir du DOM de la page.
- Appelle cette fonction avec le token résolu grâce à
execute_script. - Le JavaScript du site prend le relais : soumission du formulaire, validation ou redirection.
Implémentation Node.js (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();
})();
Implémentation PHP
L'appel à l'API est identique en PHP. En revanche, déclencher le callback exige un contexte navigateur : cet exemple couvre donc la résolution côté serveur. Utilisez ensuite un pilote de navigateur headless (par exemple php-webdriver) pour l'étape d'injection.
<?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);
}
Une fois le token obtenu en PHP, passez-le à votre outil d'automatisation du navigateur (par exemple php-webdriver) pour exécuter :
SubmitToken("TOKEN_FROM_CAPTCHAAI");
Erreurs fréquentes
| # | Erreur | Ce qui se passe | Correctif |
|---|---|---|---|
| 1 | Renseigner g-recaptcha-response au lieu d'appeler le callback |
La page ignore le token, le formulaire n'est jamais soumis | Trouvez le nom du callback et appelez-le avec le token |
| 2 | Mauvais nom de fonction | Erreur JavaScript : fonction non définie | Revérifiez data-callback, grecaptcha.render() ou la configuration interne |
| 3 | Callback sur un autre index client | Mauvaise instance reCAPTCHA ciblée sur une page multi-widgets | Vérifiez ___grecaptcha_cfg.clients[1], clients[2], etc. |
| 4 | Appel du callback avant le chargement de la page | La fonction n'est pas encore définie dans le contexte | Attendez DOMContentLoaded ou networkidle avant d'appeler |
| 5 | Nom minifié ou obfusqué | Le nom présent dans la source est illisible | Récupérez la vraie référence de fonction depuis la console, à l'exécution |
| 6 | Confusion entre callback v2 et v2 Invisible | Certaines implémentations invisibles utilisent aussi des callbacks | Vérifiez la présence de data-size="invisible" — si oui, voir Résoudre reCAPTCHA Invisible via l'API |
Dépannage
Le token est résolu mais la page ne réagit pas
Cause la plus fréquente : vous renseignez g-recaptcha-response au lieu d'appeler le callback. Vérifiez si le widget expose un data-callback ou une propriété callback dans grecaptcha.render(). Si c'est le cas, vous devez déclencher cette fonction.
ReferenceError: SubmitToken is not defined
La fonction n'est pas encore chargée, ou son nom est incorrect. Trois réflexes :
- Confirmez le nom via
data-callbackou la configuration interne. - Attendez le chargement complet de la page avant d'appeler.
- Sur un site minifié, la fonction peut être rattachée à une variable — testez
window.SubmitTokendans la console.
Le token marche sur un v2 standard mais échoue ici
Vous avez probablement affaire à une implémentation à callback. Reprenez les étapes de détection ci-dessus pour confirmer, puis basculez sur l'invocation du callback.
ERROR_BAD_TOKEN_OR_PAGEURL
La paire sitekey/pageurl est invalide. C'est une erreur d'API, sans rapport avec le choix callback ou standard. Réextrayez les deux valeurs depuis la page.
Plusieurs widgets reCAPTCHA sur la page
Chaque widget peut avoir son propre callback. Inspectez chaque <div> g-recaptcha ou parcourez ___grecaptcha_cfg.clients pour lister toutes les instances enregistrées, puis associez le bon widget au formulaire visé.
ERROR_CAPTCHA_UNSOLVABLE
Le défi n'a pas pu être résolu. Relancez une nouvelle demande. Ce cas n'est pas spécifique au callback.
Pour la liste complète des codes d'erreur, voir les erreurs fréquentes de résolution reCAPTCHA v2.
Pourquoi CaptchaAI convient à ce cas
Les chiffres ci-dessous reposent sur des mesures observées et des retours d'utilisateurs. Les résultats varient selon l'environnement, le volume et le moment de la journée.
- Même appel API — le flux envoi/interrogation est identique au reCAPTCHA v2 standard, sans aucun paramètre supplémentaire.
- Taux de réussite élevé — le callback et le v2 standard s'appuient exactement sur le même solveur.
- Temps de résolution — en général moins de 60 secondes.
- Compatibilité du token — le token renvoyé fonctionne aussi bien via l'injection
g-recaptcha-responseque via l'invocation du callback. - Tarification — plans basés sur les threads à partir de $15/mois (BASIC, 5 threads), résolutions illimitées.
Le token renvoyé par CaptchaAI est identique quelle que soit la manière dont le site intègre reCAPTCHA v2. Toute la différence se joue dans votre code côté client : la façon dont vous transmettez le token à la page.
Exemple complet, prêt à exécuter
Besoin d'un projet fonctionnel de bout en bout, avec configuration de l'environnement, interrogation du résultat, nouvelles tentatives et gestion des erreurs ?
Voir l'exemple exécutable complet sur GitHub →
FAQ
Pourquoi la page ignore-t-elle mon token dans g-recaptcha-response ?
Parce que le site attend un callback, pas une valeur de champ. Sur ce type d'implémentation, reCAPTCHA transmet le token à une fonction JavaScript enregistrée à l'avance ; tant que vous ne l'appelez pas, le champ caché reste sans effet.
Le callback s'applique-t-il aussi à reCAPTCHA v2 Invisible ?
Oui, c'est fréquent. Beaucoup d'implémentations invisibles déclenchent un callback à la place d'une soumission de formulaire classique. Vérifiez la présence de data-size="invisible" : si elle est là, appliquez la même logique d'invocation.
Que faire quand le nom du callback est minifié ou obfusqué ?
Ne vous fiez pas au nom présent dans le code source. Ouvrez la console du navigateur sur la page réelle, inspectez ___grecaptcha_cfg.clients et récupérez la référence de fonction telle qu'elle existe à l'exécution, puis appelez-la avec le token.
Combien coûte la résolution d'un reCAPTCHA v2 avec CaptchaAI ?
La facturation se fait au thread, pas au solve. Les plans démarrent à $15/mois (BASIC, 5 threads) avec des résolutions illimitées ; le callback et le v2 standard utilisent le même solveur et le même tarif.
Passez à la résolution du callback reCAPTCHA v2
- Récupérez votre clé API — captchaai.com/api.php
- Repérez le nom du callback — via
data-callback,grecaptcha.render()ou la configuration interne - Copiez le code Python ou Node.js ci-dessus — remplacez les valeurs par votre clé, votre sitekey, votre URL de page et le nom du callback
- Lancez-le — le token arrive en moins de 60 secondes, le callback se déclenche et la page traite le résultat
- Bloqué ? Consultez les erreurs fréquentes de résolution reCAPTCHA v2 ou la documentation de l'API CaptchaAI
Articles connexes
- Résoudre reCAPTCHA Invisible en Python
- Gérer reCAPTCHA v2 et Turnstile sur un même site
- Plongée dans l'API Assessment de reCAPTCHA Enterprise