{"id":24973,"date":"2026-08-06T10:17:21","date_gmt":"2026-08-06T10:17:21","guid":{"rendered":"https:\/\/capskip.com\/?p=24973"},"modified":"2026-08-06T10:17:21","modified_gmt":"2026-08-06T10:17:21","slug":"recaptcha-v2-nodejs","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/recaptcha-v2-nodejs\/","title":{"rendered":"\u5982\u4f55\u7528 Node.js \u8bc6\u522b reCAPTCHA v2\uff08\u542b\u9690\u5f62\u7248\uff09"},"content":{"rendered":"<p>reCAPTCHA v2 has three variants and they all resolve to the same Node.js method. Checkbox is the bare call, Invisible and Enterprise are options in a third argument, and the two combine. The package also ships its own TypeScript definitions, so none of this needs a separate <code>@types<\/code> install.<\/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<p>CapSkip solves on your own machine, so the desktop app has to be running first. Then point the client at the port from its settings:<\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const { CapSkip } = require('capskip');\n\nconst solver = new CapSkip({\n  apiKey: 'capskip',        \/\/ any string when key validation is off\n  host: '127.0.0.1',\n  port: 8080,\n  recaptchaTimeout: 300,    \/\/ seconds\n});<\/pre>\n<p>In production, read the connection details from the environment:<\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const solver = new CapSkip({\n  apiKey: process.env.CAPSKIP_API_KEY || 'capskip',\n  host: process.env.CAPSKIP_HOST || '127.0.0.1',\n  port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),\n});<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The three variants<\/h2>\n<table>\n<thead>\n<tr>\n<th>Variant<\/th>\n<th>Option to add<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Checkbox<\/td>\n<td>none<\/td>\n<\/tr>\n<tr>\n<td>Invisible<\/td>\n<td><code>{ invisible: 1 }<\/code><\/td>\n<\/tr>\n<tr>\n<td>Enterprise<\/td>\n<td><code>{ enterprise: 1 }<\/code><\/td>\n<\/tr>\n<tr>\n<td>Invisible Enterprise<\/td>\n<td>both keys<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">\/\/ Checkbox: sitekey and page URL, nothing else.\nconst result = await solver.recaptcha(\n  '6Lc...YOUR_SITEKEY',\n  'https:\/\/example.com\/login',\n);\n\nconsole.log(result.code);   \/\/ g-recaptcha-response token\n\n\/\/ Invisible.\nawait solver.recaptcha(sitekey, pageUrl, { invisible: 1 });\n\n\/\/ Enterprise, and both at once.\nawait solver.recaptcha(sitekey, pageUrl, { enterprise: 1 });\nawait solver.recaptcha(sitekey, pageUrl, { enterprise: 1, invisible: 1 });<\/pre>\n<p>Note the argument style. Node takes the sitekey and URL positionally, unlike the Python SDK which uses keywords. Porting code between the two is where most mistakes creep in.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">TypeScript<\/h2>\n<p>Type definitions ship with the package, so this works with no extra install:<\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">import { CapSkip, SolveResult } from 'capskip';\n\nconst solver = new CapSkip({ host: '127.0.0.1', port: 8080 });\nconst result: SolveResult = await solver.recaptcha(sitekey, pageUrl);<\/pre>\n<p><code>SolveResult<\/code> carries <code>captchaId<\/code> and <code>code<\/code> for every type, <code>userAgent<\/code> for Turnstile, and the three GeeTest fields. For reCAPTCHA only <code>code<\/code> matters.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Getting the sitekey right<\/h2>\n<p>It is the <code>data-sitekey<\/code> attribute on the widget container, or the first argument to <code>grecaptcha.render<\/code> when Invisible mode means there is no visible container. It always starts with <code>6L<\/code> and is public.<\/p>\n<p>The URL has to be the page the widget renders on. Passing a form handler or a post-login redirect is the usual cause of a token that solves cleanly and then fails verification.<\/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 body = new URLSearchParams({\n  'g-recaptcha-response': result.code,\n  username: '...',\n  password: '...',\n});\n\nconst response = await fetch('https:\/\/example.com\/login', {\n  method: 'POST',\n  body,\n});<\/pre>\n<p>Tokens are single use and last around two minutes, so solve as late as possible. If the site passes the token to a JavaScript callback rather than a form field, the solve is the same but submission differs, which our <a href=\"https:\/\/capskip.com\/recaptcha-v2-callback-solver\/\">reCAPTCHA v2 callback solver<\/a> page covers.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solving several at once<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const [a, b] = await Promise.all([\n  solver.recaptcha(sitekeyA, 'https:\/\/a.example.com'),\n  solver.recaptcha(sitekeyB, 'https:\/\/b.example.com'),\n]);\n\nconsole.log(a.code, b.code);<\/pre>\n<p>The package also exports <code>AsyncCapSkip<\/code>, but in Node it is only an alias of <code>CapSkip<\/code>. Node I\/O is already asynchronous and every method already returns a Promise, so it exists purely so code ported from the Python SDK keeps working. Switching to it gains nothing.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Errors<\/h2>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">const {\n  ValidationException, NetworkException, ApiException, TimeoutException,\n} = require('capskip');\n\ntry {\n  const result = await solver.recaptcha(sitekey, pageUrl);\n} catch (err) {\n  if (err instanceof ValidationException)      { \/* bad arguments *\/ }\n  else if (err instanceof NetworkException)    { \/* CapSkip not running *\/ }\n  else if (err instanceof ApiException)        { \/* bad sitekey or url *\/ }\n  else if (err instanceof TimeoutException)    { \/* polling timed out *\/ }\n  else throw err;\n}<\/pre>\n<p>All four extend a shared base, so catching <code>CapSkipError<\/code> handles everything in one branch if you prefer.<\/p>\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;\">Do I need to poll for the result?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">No. The client polls internally and the Promise resolves with the finished token. It starts checking after 250ms and backs off toward <code>pollingInterval<\/code>, which usually beats a hand-written loop.<\/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;\">Does it work with ESM and import syntax?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. The examples here use <code>require<\/code> for brevity, but <code>import { CapSkip } from 'capskip'<\/code> works, and the bundled TypeScript definitions come along with it.<\/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;\">Can I use this alongside Puppeteer or Playwright?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. Solve the token with this SDK, then inject it into the page with <code>page.evaluate<\/code> before submitting. Our <a href=\"https:\/\/capskip.com\/nodejs-captcha-solver\/\">Node.js CAPTCHA solver<\/a> page covers the browser automation side.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>One method, three variants, selected with <code>invisible<\/code> and <code>enterprise<\/code>. Arguments are positional, the token lands in <code>result.code<\/code>, and <code>Promise.all<\/code> is all you need for concurrency because <code>AsyncCapSkip<\/code> is just an alias here.<\/p>\n<p>The wider Node surface is on the <a href=\"https:\/\/capskip.com\/nodejs-captcha-solver\/\">Node.js CAPTCHA solver<\/a> page, other languages on the <a href=\"https:\/\/capskip.com\/recaptcha-v2-solver\/\">reCAPTCHA v2 solver<\/a> page, and there is a <a href=\"https:\/\/capskip.com\/captcha-demo\/recaptcha-v2\/\">live v2 demo<\/a> to test against. CapSkip is a <a href=\"https:\/\/capskip.com\/\">local captcha solver<\/a>, so nothing is billed per solve.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4e00\u4e2a\u65b9\u6cd5\u5373\u53ef\u8986\u76d6 Node.js \u4e2d\u7684\u5168\u90e8\u4e09\u79cd reCAPTCHA v2 \u53d8\u4f53\u3002\u4e0b\u9762\u662f\u6bcf\u79cd\u6240\u9700\u7684\u9009\u9879\uff0c\u4ee5\u53ca\u5185\u7f6e\u7684 TypeScript \u7c7b\u578b\u548c\u5982\u4f55\u5e76\u884c\u8bc6\u522b\u3002<\/p>","protected":false},"author":1,"featured_media":24972,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve reCAPTCHA v2 in Node.js | CapSkip","rank_math_description":"Checkbox, Invisible and Enterprise are one Node.js call with different options. Here is each variant, the TypeScript types, and where the token has to go.","rank_math_focus_keyword":"solve recaptcha v2 in node.js","footnotes":""},"categories":[71],"tags":[],"class_list":["post-24973","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\/24973","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=24973"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24973\/revisions"}],"predecessor-version":[{"id":24992,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24973\/revisions\/24992"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24972"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}