{"id":24981,"date":"2026-08-06T10:17:46","date_gmt":"2026-08-06T10:17:46","guid":{"rendered":"https:\/\/capskip.com\/?p=24981"},"modified":"2026-08-06T10:17:46","modified_gmt":"2026-08-06T10:17:46","slug":"recaptcha-v2-php","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/recaptcha-v2-php\/","title":{"rendered":"\u5982\u4f55\u7528 PHP \u8bc6\u522b reCAPTCHA v2\uff08\u542b\u9690\u5f62\u7248\uff09"},"content":{"rendered":"<p>reCAPTCHA v2 has three variants, and in PHP they are one method with an options array. Checkbox is the bare call, Invisible and Enterprise are flags, and both can be set together. PHP is also the one CapSkip SDK with no async story at all, which simplifies the code but changes how you should structure bulk work.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Setup<\/h2>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># PHP 8.0 or newer. Needs the curl and json extensions,\n# both of which ship with almost every PHP install.\ncomposer require capskip\/capskip<\/pre>\n<p>CapSkip solves on your own machine, so the desktop app has to be running. Point the client at the port from its settings:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">use CapSkip\\CapSkip;\n\n$solver = new CapSkip([\n    'apiKey' =&gt; 'capskip',        \/\/ any string when key validation is off\n    'host' =&gt; '127.0.0.1',\n    'port' =&gt; 8080,\n    'recaptchaTimeout' =&gt; 300,    \/\/ seconds\n]);<\/pre>\n<p>In production, read those from the environment:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">use CapSkip\\CapSkip;\n\n$solver = new CapSkip([\n    'apiKey' =&gt; getenv('CAPSKIP_API_KEY') ?: 'capskip',\n    'host' =&gt; getenv('CAPSKIP_HOST') ?: '127.0.0.1',\n    'port' =&gt; (int) (getenv('CAPSKIP_PORT') ?: 8080),\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' =&gt; 1]<\/code><\/td>\n<\/tr>\n<tr>\n<td>Enterprise<\/td>\n<td><code>['enterprise' =&gt; 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=\"php\" class=\"EnlighterJSRAW\">\/\/ Checkbox: sitekey and page URL only.\n$result = $solver-&gt;recaptcha(\n    '6Lc...YOUR_SITEKEY',\n    'https:\/\/example.com\/login'\n);\n\necho $result['code'];   \/\/ g-recaptcha-response token\n\n\/\/ Invisible.\n$result = $solver-&gt;recaptcha($sitekey, $pageUrl, ['invisible' =&gt; 1]);\n\n\/\/ Enterprise, and both together.\n$result = $solver-&gt;recaptcha($sitekey, $pageUrl, ['enterprise' =&gt; 1]);\n$result = $solver-&gt;recaptcha($sitekey, $pageUrl, [\n    'enterprise' =&gt; 1,\n    'invisible' =&gt; 1,\n]);<\/pre>\n<p>The return value is an associative array, so <code>$result['code']<\/code> is the token. <code>$result['captchaId']<\/code> is there too if you want to log which solve produced it.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Getting the inputs right<\/h2>\n<p>The sitekey 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 container to look at. It always starts with <code>6L<\/code> and is public.<\/p>\n<p>The URL must be the page the widget renders on. Passing your form handler or a post-login redirect is the usual cause of a token that solves fine 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=\"php\" class=\"EnlighterJSRAW\">$ch = curl_init('https:\/\/example.com\/login');\n\ncurl_setopt_array($ch, [\n    CURLOPT_POST =&gt; true,\n    CURLOPT_RETURNTRANSFER =&gt; true,\n    CURLOPT_POSTFIELDS =&gt; http_build_query([\n        'g-recaptcha-response' =&gt; $result['code'],\n        'username' =&gt; '...',\n        'password' =&gt; '...',\n    ]),\n]);\n\n$response = curl_exec($ch);\ncurl_close($ch);<\/pre>\n<p>Tokens are single use and last about two minutes, so solve as late in the flow as possible. If the site hands the token to a JavaScript callback instead of a form field, the solve is identical but submission differs. Our <a href=\"https:\/\/capskip.com\/recaptcha-v2-callback-solver\/\">reCAPTCHA v2 callback solver<\/a> page covers that shape.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Errors, and the namespace people miss<\/h2>\n<p>PHP puts its exceptions under their own namespace, which trips up anyone copying imports from the Python or Node examples:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">use CapSkip\\CapSkip;\nuse CapSkip\\Exceptions\\ValidationException;\nuse CapSkip\\Exceptions\\NetworkException;\nuse CapSkip\\Exceptions\\ApiException;\nuse CapSkip\\Exceptions\\TimeoutException;\n\ntry {\n    $result = $solver-&gt;recaptcha($sitekey, $pageUrl);\n} catch (ValidationException $e) {\n    \/\/ missing or malformed arguments\n} catch (NetworkException $e) {\n    \/\/ CapSkip is not running on the configured port\n} catch (ApiException $e) {\n    \/\/ the sitekey or pageurl was rejected\n} catch (TimeoutException $e) {\n    \/\/ exceeded recaptchaTimeout\n}<\/pre>\n<p>All four extend <code>CapSkip\\Exceptions\\CapSkipError<\/code>, so a single catch on the base class handles everything if you would rather deal with failures in one place.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solving several<\/h2>\n<p>PHP executes synchronously, so each solve blocks until it finishes:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">use CapSkip\\CapSkip;\n\n$solver = new CapSkip();\n\nforeach ($targets as $target) {\n    $results[] = $solver-&gt;recaptcha($target['sitekey'], $target['url']);\n}<\/pre>\n<p>The package exports <code>AsyncCapSkip<\/code>, but in PHP it is only an alias so that code ported from the other SDKs keeps running. It does not add concurrency. For genuine parallelism, run multiple worker processes or use a queue rather than expecting the SDK to overlap solves.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Using a proxy<\/h2>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">$result = $solver-&gt;recaptcha($sitekey, $pageUrl, [\n    'proxy' =&gt; ['type' =&gt; 'HTTPS', 'uri' =&gt; 'user:pass@1.2.3.4:3128'],\n]);<\/pre>\n<p>Proxies are supported for reCAPTCHA, Turnstile and GeeTest, but not for image CAPTCHAs, which are solved from the image bytes and never reach the target site.<\/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 SDK polls internally and returns the finished token, so <code>CAPCHA_NOT_READY<\/code> never reaches your code. It begins checking after 250ms and backs off from there.<\/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;\">Will a solve block my web request?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes, and that matters in PHP. A reCAPTCHA solve can take several seconds, so doing it inside a page render ties up a worker. Push solving into a queued job or a CLI worker rather than blocking a request thread.<\/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 are the requirements?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">PHP 8.0 or newer with the <code>curl<\/code> and <code>json<\/code> extensions, both bundled with most installs. There are no other runtime dependencies, so it drops into an existing project without pulling a tree of packages.<\/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> in the options array. Read <code>$result['code']<\/code>, submit it as <code>g-recaptcha-response<\/code>, import exceptions from <code>CapSkip\\Exceptions<\/code>, and keep solves off your request threads.<\/p>\n<p>The wider PHP surface is on the <a href=\"https:\/\/capskip.com\/php-captcha-solver\/\">PHP 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>\u4e09\u79cd reCAPTCHA v2 \u53d8\u4f53\u90fd\u662f\u540c\u4e00\u4e2a PHP \u65b9\u6cd5\uff0c\u53ea\u662f\u6570\u7ec4\u9009\u9879\u4e0d\u540c\u3002\u4e0b\u9762\u9010\u4e00\u8bf4\u660e\uff0c\u8fd8\u6709\u4eba\u4eec\u5e38\u5ffd\u7565\u7684\u5f02\u5e38\u547d\u540d\u7a7a\u95f4\u3002<\/p>","protected":false},"author":1,"featured_media":24980,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve reCAPTCHA v2 in PHP | CapSkip","rank_math_description":"Checkbox, Invisible and Enterprise are one PHP call with different options. Here is each variant, the exception namespace, and where the token has to go.","rank_math_focus_keyword":"solve recaptcha v2 in php","footnotes":""},"categories":[71],"tags":[],"class_list":["post-24981","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\/24981","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=24981"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24981\/revisions"}],"predecessor-version":[{"id":24996,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24981\/revisions\/24996"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24980"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24981"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24981"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24981"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}