{"id":24975,"date":"2026-08-06T10:17:28","date_gmt":"2026-08-06T10:17:28","guid":{"rendered":"https:\/\/capskip.com\/?p=24975"},"modified":"2026-08-06T10:17:28","modified_gmt":"2026-08-06T10:17:28","slug":"recaptcha-v3-nodejs","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/recaptcha-v3-nodejs\/","title":{"rendered":"\u5982\u4f55\u7528 Node.js \u8bc6\u522b reCAPTCHA v3 \u5e76\u8bbe\u7f6e action"},"content":{"rendered":"<p>reCAPTCHA v3 never shows a puzzle. It scores the visit silently and hands the page a token, which the site verifies on its own backend. From your side there is nothing to click, so the entire job is producing a token the site will accept. In Node.js that is the same <code>recaptcha<\/code> method used for v2, with one extra option.<\/p>\n<p>The detail that decides whether it works is the action.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Setup<\/h2>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\">npm install capskip<\/pre>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const { CapSkip } = require('capskip');\n\nconst solver = new CapSkip({\n  host: '127.0.0.1',\n  port: 8080,\n  recaptchaTimeout: 300,   \/\/ seconds, shared with Turnstile and GeeTest\n});<\/pre>\n<p>CapSkip runs locally, so the desktop app needs to be open before any call succeeds.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The basic call<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const result = await solver.recaptcha(\n  '6Lc...YOUR_SITEKEY',\n  'https:\/\/example.com\/checkout',\n  { version: 'v3', action: 'submit' },\n);\n\nconsole.log(result.code);   \/\/ the v3 token<\/pre>\n<p>Two differences from v2: <code>version: 'v3'<\/code> is required, and <code>action<\/code> should match what the page passes to <code>grecaptcha.execute<\/code>. Leave it out and it defaults to <code>verify<\/code>.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Why the action matters<\/h2>\n<p>Actions are labels a site attaches to each protected interaction so a login and a checkout can be scored independently. Most backends check that the action on the token matches the one they expected for that endpoint.<\/p>\n<p>Send the wrong label and the token is genuine but arrives tagged for a different interaction, which many verifiers reject. Read the real value from the page rather than guessing:<\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const html = await (await fetch('https:\/\/example.com\/checkout')).text();\n\n\/\/ Sites normally call execute() with the action as a string literal.\nconst match = html.match(\/execute\\([^,]+,\\s*\\{\\s*action:\\s*['&quot;]([^'&quot;]+)\/);\nconst action = match ? match[1] : 'verify';\n\nconst result = await solver.recaptcha(sitekey, pageUrl, {\n  version: 'v3',\n  action,\n});<\/pre>\n<p>Common values are <code>login<\/code>, <code>submit<\/code>, <code>homepage<\/code> and <code>checkout<\/code>, but they are arbitrary strings chosen by whoever built the site.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Enterprise v3<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const result = await solver.recaptcha(sitekey, pageUrl, {\n  version: 'v3',\n  enterprise: 1,\n  action: 'submit',\n});<\/pre>\n<p>Enterprise is an orthogonal flag rather than a different product, so it simply stacks. Tell the two apart by the script the page loads: Enterprise pulls <code>enterprise.js<\/code>, standard pulls <code>api.js<\/code>. A wrong guess fails the solve rather than returning a bad token, so it is cheap to check.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Submitting the token<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const response = await fetch('https:\/\/example.com\/checkout', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application\/json' },\n  body: JSON.stringify({\n    token: result.code,\n    orderId: '...',\n  }),\n});<\/pre>\n<p>Unlike v2 there is no standard form widget, so v3 integrations vary. Some use a hidden <code>g-recaptcha-response<\/code> field, others post JSON with a custom key. Check what the page&#8217;s own JavaScript does before assuming a field name.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solving in bulk<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const tokens = await Promise.all(\n  urls.map((url) =&gt;\n    solver.recaptcha(sitekey, url, { version: 'v3', action: 'submit' })),\n);\n\nconsole.log(tokens.map((t) =&gt; t.code));<\/pre>\n<p><code>AsyncCapSkip<\/code> exists in this package too, but it is an alias of <code>CapSkip<\/code>. Every method already returns a Promise, so <code>Promise.all<\/code> is the whole concurrency story in Node.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Errors<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const { ApiException, NetworkException } = require('capskip');\n\ntry {\n  const result = await solver.recaptcha(sitekey, pageUrl, { version: 'v3' });\n} catch (err) {\n  if (err instanceof NetworkException) {\n    \/\/ CapSkip is not running on the configured port\n  } else if (err instanceof ApiException) {\n    \/\/ the sitekey or pageurl was rejected\n  } else {\n    throw err;\n  }\n}<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Frequently asked questions<\/h2>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Can I see the score before submitting?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">No. The score stays with Google and is only revealed to the site owner when their backend verifies the token. From the client side you receive a token and nothing else, so there is no way to inspect or filter on a score first.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">What if the page has no visible action?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Some pages call <code>execute<\/code> without one, in which case the default <code>verify<\/code> is correct. If the regex above finds nothing, that is usually why rather than a parsing failure.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">How long does a v3 token last?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Roughly two minutes, single use, same as v2. Solve immediately before the request that needs it rather than building a pool.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>Pass <code>version: 'v3'<\/code>, set <code>action<\/code> to whatever the page actually executes, add <code>enterprise: 1<\/code> when it loads <code>enterprise.js<\/code>, and submit quickly because the token expires in about two minutes.<\/p>\n<p>Other languages are covered on the <a href=\"https:\/\/capskip.com\/recaptcha-v3-solver\/\">reCAPTCHA v3 solver<\/a> page, Enterprise specifics on the <a href=\"https:\/\/capskip.com\/recaptcha-enterprise-solver\/\">Enterprise solver<\/a> page, and the wider Node surface on the <a href=\"https:\/\/capskip.com\/nodejs-captcha-solver\/\">Node.js CAPTCHA solver<\/a> page. Watch a real token appear on our <a href=\"https:\/\/capskip.com\/captcha-demo\/recaptcha-v3\/\">v3 demo<\/a>. CapSkip is a <a href=\"https:\/\/capskip.com\/\">captcha solver<\/a> that runs on your own machine.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>v3 \u4e0e v2 \u662f\u540c\u4e00\u4e2a Node.js \u65b9\u6cd5\uff0c\u53ea\u591a\u4e86\u4e00\u4e2a version \u6807\u5fd7\u3002\u8ba9\u4eba\u8e29\u5751\u7684\u662f action\uff0c\u5b83\u5fc5\u987b\u4e0e\u9875\u9762\u5b9e\u9645\u6267\u884c\u7684\u5185\u5bb9\u4e00\u81f4\u3002<\/p>","protected":false},"author":1,"featured_media":24974,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve reCAPTCHA v3 in Node.js | CapSkip","rank_math_description":"reCAPTCHA v3 in Node.js needs a version flag and an action matching the page. Here is the call for standard and Enterprise, plus how to find the action.","rank_math_focus_keyword":"solve recaptcha v3 in node.js","footnotes":""},"categories":[71],"tags":[],"class_list":["post-24975","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-google-recaptcha"],"_links":{"self":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/comments?post=24975"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24975\/revisions"}],"predecessor-version":[{"id":24993,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24975\/revisions\/24993"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24974"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}