分数 0.9、success 为 true,表单一提交却回 403 —— 接入 reCAPTCHA v3 最常见的卡点,问题几乎都出在 action:提交的值和后端预期的对不上。
action 是标明校验场景的字符串,如 login、checkout、submit_form。它写进 token,后端逐字比对,对不上就丢弃 —— 和 sitekey、pageurl 一样必填。
30 秒排查清单
“分数正常但被拒”按顺序查,多数在第二步定位:
- 确认
grecaptcha已加载,token 不是空串 - 找出前端
grecaptcha.execute()传入的 action - 原样填进识别请求,大小写、下划线都别改
action 在一次 v3 校验中的位置
前端调用 grecaptcha.execute() 时带上 action:
grecaptcha.execute('SITEKEY', { action: 'submit_form' });
该字符串嵌入 token。后端拿 token 向 Google 校验,响应如下:
{
"success": true,
"score": 0.9,
"action": "submit_form",
"challenge_ts": "2024-01-15T12:00:00Z",
"hostname": "example.com"
}
决定放行的是 response.action === 'submit_form'。填成 action: 'homepage' 时 token 依然合法、分数也不低,但比对失败,后端通常只回 403。
排查顺序:先看
success,再看 score,最后看 action。前两项正常不代表 token 能用。
三种方法定位站点真实的 action
哪个先出结果用哪个:
方法 1:网络面板抓调用
- 打开 DevTools → 网络 面板
- 用
recaptcha或anchor过滤请求 - 触发表单提交
- 在“发起者”列找
grecaptcha.execute调用栈,action 就在参数里
方法 2:控制台扫内联脚本
// In the browser console:
document.querySelectorAll('script').forEach(s => {
if (s.textContent.includes('action')) {
const match = s.textContent.match(/action['":\s]+['"](\w+)['"]/);
if (match) console.log('Found action:', match[1]);
}
});
方法 3:Sources 全局搜索
- 打开 DevTools → Sources 面板
- 按
Ctrl + Shift + F全局搜索grecaptcha.execute或action: - 选项对象里的那个字符串就是 action
内联脚本压缩后常搜不到,但 action 在构建产物里一般是明文。
把 action 带进 CaptchaAI 识别请求
把 action 与 googlekey、pageurl 一起提交到 in.php,再每 5 秒轮询 res.php。
Python
import requests
import time
response = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"version": "v3",
"googlekey": "6LfZil0UAAAAADM1Dpz...",
"action": "login", # Must match the site's action
"pageurl": "https://staging.example.com/qa-login",
"json": 1
})
task_id = response.json()["request"]
for _ in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY", "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
token = result["request"]
break
res.php 的 action=get 是 CaptchaAI 接口自身的动作名,与 reCAPTCHA 的 action 同名不同义,别共用变量。
Node.js
const axios = require('axios');
async function solveRecaptchaV3(sitekey, pageurl, action) {
const submit = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: 'YOUR_API_KEY',
method: 'userrecaptcha',
version: 'v3',
googlekey: sitekey,
action: action,
pageurl: pageurl,
json: 1
}
});
const taskId = submit.data.request;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 5000));
const result = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (result.data.status === 1) return result.data.request;
}
throw new Error('Timeout waiting for solution');
}
solveRecaptchaV3('6LfZil0UAAAAADM1Dpz...', 'https://staging.example.com/qa-login', 'login')
.then(token => console.log('Token:', token));
建议把 action 按环境和路由整理成配置表,别写死在脚本里:改版换名很常见。
取值速查
常见 action 取值
| action 值 | 典型场景 |
|---|---|
homepage |
落地页访问 |
login |
登录表单 |
register |
账号注册 |
checkout |
支付页 |
传错值分别会发生什么
| 情况 | 结果 |
|---|---|
| 与站点一致 | 通过校验,请求放行 |
| 写错 | token 有效,但值不匹配被拒 |
| 留空 | 部分站点放行,部分直接拒绝 |
| 找不到 | 先用 homepage 兜底 |
一个国内团队踩过的坑
深圳一家跨境电商团队跑注册回归测试:staging 的 action 是 register,灰度页面复用同一模板却写成 signup,结果 staging 全绿、灰度全挂,日志只有 403。这类排查还有两点:
- reCAPTCHA 依赖 Google 托管脚本,内地网络加载不稳定,超时易被误判成 action 配错
- CaptchaAI 按线程计费而非按次计费(BASIC $15/月,5 线程),反复核对不额外计费
把回显的 action 写进日志并与提交值做断言,改版当天就能发现。
常见问题
后端回显的 action 和我提交的不一致,是识别错了吗?
不是。Google 会把 token 里的 action 原样回显。不一致说明提交的值和页面调用的不同,回 grecaptcha.execute 处核对即可。
action 区分大小写吗?下划线能换连字符吗?
区分,也不能换。后端做字符串全等比较,Submit_Form 与 submit_form 是两个值,务必复制原文。
单页应用里 action 动态传入,怎么抓?
在 grecaptcha.execute 上打断点或监听 XHR,就能看到调用时机和传入的 action。SPA 同一页面常有多个 action,别抓第一个就收工。