Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
reCAPTCHA PHP Client Library
https://github.com/google/recaptcha
Admin
A PHP library that wraps Google's reCAPTCHA server-side verification for protecting websites from
...
Tokens:
12,763
Snippets:
87
Trust Score:
9.6
Update:
1 week ago
Context
Skills
Chat
Benchmark
93.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# google/recaptcha — PHP Client Library The `google/recaptcha` library (v1.5.0) is the official PHP client for Google's reCAPTCHA service, a free tool that protects websites from spam and abuse by distinguishing human users from automated bots. It provides a clean, object-oriented API for server-side verification of reCAPTCHA tokens generated by the browser-side widget. The library supports all current reCAPTCHA flavors: v2 checkbox ("I'm not a robot"), v2 invisible (triggered on form submit), and v3 (score-based, frictionless). Install via Composer with `composer require google/recaptcha "^1.5"` (requires PHP ≥ 8.4). The library's design centres on a single `ReCaptcha` client class that sends the user's browser-generated token to Google's `siteverify` API and returns a typed `Response` object. Server-to-Google communication is handled through swappable `RequestMethod` implementations (cURL, PHP streams, or raw socket), selected automatically based on available PHP extensions. A fluent configuration API lets you enforce additional constraints — hostname, action name, score threshold, and challenge age — before accepting a submission as legitimate. --- ## Installation ```bash composer require google/recaptcha "^1.5" ``` ```php // Via Composer autoloader require_once 'vendor/autoload.php'; // Or via the bundled autoloader (non-Composer) require_once 'src/autoload.php'; ``` --- ## `ReCaptcha::__construct()` — Create a configured client instance Instantiates the reCAPTCHA client with your site's secret key. If cURL is available it is used automatically; otherwise the library falls back to PHP's `file_get_contents`. A custom `RequestMethod` can be injected as the optional second argument to override the transport entirely. ```php use ReCaptcha\ReCaptcha; use ReCaptcha\RequestMethod\SocketPost; // Default: auto-selects CurlPost if curl extension present, else Post (file_get_contents) $recaptcha = new ReCaptcha('YOUR_SECRET_KEY'); // Explicit cURL transport $recaptcha = new ReCaptcha('YOUR_SECRET_KEY', new \ReCaptcha\RequestMethod\CurlPost()); // Socket transport — use on hosts where allow_url_fopen is disabled $recaptcha = new ReCaptcha('YOUR_SECRET_KEY', new SocketPost()); // Empty secret throws immediately try { $bad = new ReCaptcha(''); } catch (\RuntimeException $e) { echo $e->getMessage(); // "No secret provided" } ``` --- ## `ReCaptcha::verify()` — Verify a user response token Calls Google's `siteverify` endpoint with the token submitted in the `g-recaptcha-response` form field and optionally the end-user's IP address. Returns a `Response` object. Any additional constraints set via the fluent setters are evaluated here; failures are appended to the response's error codes and the response is marked unsuccessful. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); // $token comes from $_POST['g-recaptcha-response'] in the browser form $token = $_POST[ReCaptcha::RESPONSE_KEY] ?? ''; $remoteIp = $_SERVER['REMOTE_ADDR'] ?? null; $resp = $recaptcha->verify($token, $remoteIp); if ($resp->isSuccess()) { // Token is valid — proceed with the form action echo "Verification passed!"; echo " Hostname: " . $resp->getHostname(); // e.g. "www.example.com" echo " Challenge TS: " . $resp->getChallengeTs(); // e.g. "2024-03-15T10:30:00Z" } else { // One or more error codes explain the failure $errors = $resp->getErrorCodes(); // Possible codes: missing-input-response, invalid-input-response, // bad-request, invalid-input-secret, timeout-or-duplicate, // hostname-mismatch, action-mismatch, score-threshold-not-met, // challenge-timeout, connection-failed, invalid-json http_response_code(400); echo "Failed: " . implode(', ', $errors); } ``` --- ## `ReCaptcha::setExpectedHostname()` — Enforce a hostname constraint Instructs `verify()` to compare the hostname embedded in Google's response against a provided expected value (case-insensitive). If they do not match, `E_HOSTNAME_MISMATCH` is added to the error codes. This guards against token replay attacks from other domains. Returns `$this` for method chaining. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); // Only accept tokens solved on www.example.com $resp = $recaptcha ->setExpectedHostname('www.example.com') ->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); if (!$resp->isSuccess()) { if (in_array(ReCaptcha::E_HOSTNAME_MISMATCH, $resp->getErrorCodes())) { echo "Token was not solved on this domain."; } } ``` --- ## `ReCaptcha::setExpectedAction()` — Enforce an action name constraint (v3) For reCAPTCHA v3, each page can tag its token with a named action (e.g. `login`, `checkout`). This setter makes `verify()` reject responses whose recorded action does not match (case-insensitive), adding `E_ACTION_MISMATCH` on failure. Returns `$this`. ```php use ReCaptcha\ReCaptcha; // Front-end sets: grecaptcha.execute(SITE_KEY, {action: 'login'}) $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_V3_SECRET']); $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->setExpectedAction('login') ->verify($_GET['token'], $_SERVER['REMOTE_ADDR']); if (!$resp->isSuccess()) { $errors = $resp->getErrorCodes(); // 'action-mismatch' present if the JS used a different action name echo json_encode(['success' => false, 'errors' => $errors]); exit; } echo json_encode(['success' => true, 'action' => $resp->getAction()]); ``` --- ## `ReCaptcha::setScoreThreshold()` — Enforce a minimum score (v3) Sets a minimum acceptable score (0.0–1.0). Responses with a score below the threshold have `E_SCORE_THRESHOLD_NOT_MET` appended. Scores closer to 1.0 indicate a likely human interaction; scores near 0.0 indicate a likely bot. Returns `$this`. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_V3_SECRET']); // Require at least 0.5 confidence — adjust based on your traffic distribution $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->setExpectedAction('checkout') ->setScoreThreshold(0.5) ->verify($_POST['recaptcha_token'], $_SERVER['REMOTE_ADDR']); header('Content-Type: application/json'); if ($resp->isSuccess()) { echo json_encode([ 'success' => true, 'score' => $resp->getScore(), // e.g. 0.9 'action' => $resp->getAction(), // e.g. "checkout" ]); } else { echo json_encode([ 'success' => false, 'errors' => $resp->getErrorCodes(), // may contain 'score-threshold-not-met' ]); } ``` --- ## `ReCaptcha::setChallengeTimeout()` — Enforce a maximum token age Rejects tokens whose `challenge_ts` (the time the widget loaded in the browser) is older than the given number of seconds, adding `E_CHALLENGE_TIMEOUT` to the error codes. Useful to prevent stale token replay. Returns `$this`. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); // Reject tokens older than 5 minutes (300 seconds) $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->setChallengeTimeout(300) ->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); if (!$resp->isSuccess()) { $errors = $resp->getErrorCodes(); if (in_array(ReCaptcha::E_CHALLENGE_TIMEOUT, $errors)) { echo "The CAPTCHA has expired. Please reload the page and try again."; } else { echo "Verification failed: " . implode(', ', $errors); } } ``` --- ## `ReCaptcha::setExpectedApkPackageName()` — Enforce an Android APK package name For reCAPTCHA tokens generated in Android apps, this validates the APK package name embedded in the response. Mismatches produce `E_APK_PACKAGE_NAME_MISMATCH`. Returns `$this`. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); $resp = $recaptcha ->setExpectedApkPackageName('com.example.myapp') ->verify($tokenFromAndroidClient, null); if ($resp->isSuccess()) { echo "Android token verified for: " . $resp->getApkPackageName(); } else { echo "Failed: " . implode(', ', $resp->getErrorCodes()); } ``` --- ## `Response` class — Inspecting the verification result `Response` is a readonly value object returned by `verify()`. It exposes typed accessors for every field in Google's JSON response, plus a convenience `toArray()` method for JSON serialization. ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); $resp = $recaptcha->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); // Boolean pass/fail $resp->isSuccess(); // true | false // Error details (empty array on success) $resp->getErrorCodes(); // e.g. ['invalid-input-response', 'hostname-mismatch'] // Metadata from Google $resp->getHostname(); // "www.example.com" $resp->getChallengeTs(); // "2024-03-15T10:30:00Z" (ISO 8601) $resp->getScore(); // 0.9 (float, v3 only; null for v2) $resp->getAction(); // "login" (string, v3 only; "" for v2) $resp->getApkPackageName(); // "com.example.app" (Android only; "" otherwise) // Full array representation — ready for json_encode() $array = $resp->toArray(); // [ // 'success' => true, // 'hostname' => 'www.example.com', // 'challenge_ts' => '2024-03-15T10:30:00Z', // 'apk_package_name' => '', // 'score' => 0.9, // 'action' => 'login', // 'error-codes' => [], // ] header('Content-Type: application/json'); echo json_encode($resp->toArray()); ``` --- ## `RequestMethod\CurlPost` — cURL-based HTTP transport Sends the verification request to Google using cURL (requires the PHP `curl` extension). This is the default transport when cURL is available. The verify URL can be overridden in the constructor, which is useful for testing with a mock server. ```php use ReCaptcha\ReCaptcha; use ReCaptcha\RequestMethod\CurlPost; // Standard usage — uses default Google URL $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET'], new CurlPost()); // Override verify URL for integration testing $mockUrl = 'http://localhost:8080/recaptcha/api/siteverify'; $recaptcha = new ReCaptcha('test-secret', new CurlPost($mockUrl)); $resp = $recaptcha->verify('test-token'); var_dump($resp->isSuccess()); // depends on mock server response ``` --- ## `RequestMethod\Post` — `file_get_contents`-based HTTP transport Sends the verification POST using PHP's `file_get_contents()` with a stream context (SSL peer verification enabled, 60-second timeout). Used automatically when cURL is not available. Requires `allow_url_fopen = On` in `php.ini`. ```php use ReCaptcha\ReCaptcha; use ReCaptcha\RequestMethod\Post; // Explicit Post transport (useful when cURL is installed but you want to avoid it) $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET'], new Post()); $resp = $recaptcha ->setExpectedHostname('www.example.com') ->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); echo $resp->isSuccess() ? 'OK' : implode(', ', $resp->getErrorCodes()); ``` --- ## `RequestMethod\SocketPost` — `fsockopen`-based HTTP transport Sends the verification POST over a raw TLS socket using `fsockopen()`. Use this on servers where `allow_url_fopen` is disabled, preventing both cURL-less `Post` and any `file_get_contents`-based HTTP. Returns `connection-failed` or `bad-response` error codes on network or HTTP-level failures. ```php use ReCaptcha\ReCaptcha; use ReCaptcha\RequestMethod\SocketPost; // Use when both cURL and allow_url_fopen are unavailable $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET'], new SocketPost()); $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); if ($resp->isSuccess()) { // Proceed } else { // 'connection-failed' or 'bad-response' indicate transport problems error_log('reCAPTCHA error: ' . implode(', ', $resp->getErrorCodes())); } ``` --- ## `RequestParameters` — Building the request payload A readonly value object encapsulating the four fields sent to Google's siteverify API: `secret`, `response` token, optional `remoteip`, and client library `version`. Provides `toArray()` and `toQueryString()` serialisation. Normally constructed internally by `ReCaptcha::verify()`, but can be used directly when implementing a custom `RequestMethod`. ```php use ReCaptcha\RequestParameters; use ReCaptcha\ReCaptcha; // Internal construction (how ReCaptcha::verify() uses it) $params = new RequestParameters( secret: 'YOUR_SECRET', response: '03AGdBq24...', // token from browser remoteIp: '203.0.113.42', version: ReCaptcha::VERSION // 'php_1.5.0' ); // Array form — sent as POST body fields print_r($params->toArray()); // Array ( [secret] => YOUR_SECRET [response] => 03AGdBq24... [remoteip] => 203.0.113.42 [version] => php_1.5.0 ) // Query-string form used by all RequestMethod implementations echo $params->toQueryString(); // secret=YOUR_SECRET&response=03AGdBq24...&remoteip=203.0.113.42&version=php_1.5.0 ``` --- ## v2 Checkbox — Complete form integration example Full server-side handler for the classic reCAPTCHA v2 "I'm not a robot" checkbox widget. The widget injects the `g-recaptcha-response` field into the form on completion. ```php <?php require_once 'vendor/autoload.php'; use ReCaptcha\ReCaptcha; $secret = 'YOUR_V2_SECRET_KEY'; $siteKey = 'YOUR_V2_SITE_KEY'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $recaptcha = new ReCaptcha($secret); $token = $_POST[ReCaptcha::RESPONSE_KEY] ?? ''; $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->verify($token, $_SERVER['REMOTE_ADDR']); if ($resp->isSuccess()) { echo "Form submitted successfully!"; // process form... } else { $errors = implode(', ', $resp->getErrorCodes()); echo "reCAPTCHA failed: {$errors}"; } exit; } ?> <form method="post"> <input type="text" name="email" placeholder="Email"> <div class="g-recaptcha" data-sitekey="<?= htmlspecialchars($siteKey) ?>"></div> <button type="submit">Submit</button> </form> <script src="https://www.google.com/recaptcha/api.js" async defer></script> ``` --- ## v2 Invisible — Automatic trigger on form submit The invisible reCAPTCHA runs silently on form submission via a `data-callback` attribute on the submit button; no checkbox is shown to the user. ```php <?php require_once 'vendor/autoload.php'; use ReCaptcha\ReCaptcha; $secret = 'YOUR_V2_INVISIBLE_SECRET'; $siteKey = 'YOUR_V2_INVISIBLE_SITE_KEY'; if (isset($_POST[ReCaptcha::RESPONSE_KEY])) { $recaptcha = new ReCaptcha($secret); $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->verify($_POST[ReCaptcha::RESPONSE_KEY], $_SERVER['REMOTE_ADDR']); echo $resp->isSuccess() ? 'OK' : implode(', ', $resp->getErrorCodes()); exit; } ?> <form method="post" id="demo-form"> <input type="text" name="username"> <!-- data-callback fires after reCAPTCHA completes; it then submits the form --> <button class="g-recaptcha" data-sitekey="<?= htmlspecialchars($siteKey) ?>" data-callback="onSubmit" type="submit">Submit</button> </form> <script src="https://www.google.com/recaptcha/api.js" async defer></script> <script> function onSubmit(token) { document.getElementById('demo-form').submit(); } </script> ``` --- ## v3 Score endpoint — Token verification API endpoint For reCAPTCHA v3, the browser calls `grecaptcha.execute()` to obtain a token, then POSTs or GETs it to a PHP endpoint that verifies it and returns a JSON response with the score and action. ```php <?php // recaptcha-verify.php — called by the browser via fetch() require_once 'vendor/autoload.php'; use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_V3_SECRET']); $token = $_GET['token'] ?? ''; $action = $_GET['action'] ?? ''; $resp = $recaptcha ->setExpectedHostname($_SERVER['SERVER_NAME']) ->setExpectedAction($action) ->setScoreThreshold(0.5) ->verify($token, $_SERVER['REMOTE_ADDR']); header('Content-Type: application/json'); echo json_encode($resp->toArray()); // {"success":true,"hostname":"www.example.com","challenge_ts":"2024-03-15T10:30:00Z", // "apk_package_name":"","score":0.9,"action":"checkout","error-codes":[]} ``` ```html <!-- Front-end: include the v3 library with render=SITE_KEY --> <script src="https://www.google.com/recaptcha/api.js?render=YOUR_V3_SITE_KEY"></script> <script> grecaptcha.ready(function() { grecaptcha.execute('YOUR_V3_SITE_KEY', {action: 'checkout'}).then(function(token) { fetch('/recaptcha-verify.php?action=checkout&token=' + token) .then(r => r.json()) .then(data => console.log(data)); }); }); </script> ``` --- ## Content Security Policy integration When deploying a strict CSP, use a per-request `nonce` on the reCAPTCHA `<script>` tag and `'strict-dynamic'` so Google's own dynamically loaded scripts are also permitted. ```php <?php require_once 'vendor/autoload.php'; // Generate a cryptographically random nonce for this response $nonce = base64_encode(openssl_random_pseudo_bytes(16)); header( 'Content-Security-Policy: ' . "default-src 'none'; " . "script-src 'nonce-{$nonce}' 'strict-dynamic'; " . 'img-src https://www.gstatic.com/recaptcha/ https://www.google-analytics.com; ' . 'frame-src https://www.google.com/; ' . "style-src 'self'; " . "connect-src 'self';" ); $siteKey = $_ENV['RECAPTCHA_V3_SITE']; ?> <!-- Attach the nonce to the reCAPTCHA script tag --> <script nonce="<?= $nonce ?>" async defer src="https://www.google.com/recaptcha/api.js?render=<?= htmlspecialchars($siteKey) ?>&onload=onloadCallback"> </script> <script nonce="<?= $nonce ?>"> var onloadCallback = function() { grecaptcha.ready(function() { grecaptcha.execute('<?= htmlspecialchars($siteKey) ?>', {action: 'pageview'}) .then(function(token) { fetch('/recaptcha-verify.php?action=pageview&token=' + token) .then(r => r.json()).then(console.log); }); }); }; </script> ``` --- ## Error code reference All error code constants are defined on the `ReCaptcha` class: | Constant | String value | Meaning | |---|---|---| | `ReCaptcha::E_MISSING_INPUT_RESPONSE` | `missing-input-response` | Token field was empty | | `ReCaptcha::E_INVALID_JSON` | `invalid-json` | Could not decode Google's response | | `ReCaptcha::E_CONNECTION_FAILED` | `connection-failed` | Could not reach Google's API | | `ReCaptcha::E_BAD_RESPONSE` | `bad-response` | Non-200 HTTP status from Google | | `ReCaptcha::E_UNKNOWN_ERROR` | `unknown-error` | Response was not success but no codes given | | `ReCaptcha::E_HOSTNAME_MISMATCH` | `hostname-mismatch` | `setExpectedHostname()` constraint violated | | `ReCaptcha::E_APK_PACKAGE_NAME_MISMATCH` | `apk_package_name-mismatch` | `setExpectedApkPackageName()` constraint violated | | `ReCaptcha::E_ACTION_MISMATCH` | `action-mismatch` | `setExpectedAction()` constraint violated | | `ReCaptcha::E_SCORE_THRESHOLD_NOT_MET` | `score-threshold-not-met` | v3 score below `setScoreThreshold()` value | | `ReCaptcha::E_CHALLENGE_TIMEOUT` | `challenge-timeout` | Token older than `setChallengeTimeout()` seconds | ```php use ReCaptcha\ReCaptcha; $recaptcha = new ReCaptcha($_ENV['RECAPTCHA_SECRET']); $resp = $recaptcha->verify($_POST[ReCaptcha::RESPONSE_KEY] ?? '', $_SERVER['REMOTE_ADDR']); foreach ($resp->getErrorCodes() as $code) { match ($code) { ReCaptcha::E_HOSTNAME_MISMATCH => error_log("Possible token replay from wrong domain"), ReCaptcha::E_SCORE_THRESHOLD_NOT_MET => error_log("Likely bot — score: " . $resp->getScore()), ReCaptcha::E_CHALLENGE_TIMEOUT => error_log("Stale token submitted"), ReCaptcha::E_CONNECTION_FAILED => error_log("Could not reach Google API"), default => error_log("reCAPTCHA error: $code"), }; } ``` --- ## Summary The `google/recaptcha` library covers the three principal server-side integration scenarios: traditional v2 checkbox forms (where the user actively checks a box before submission), v2 invisible (where the challenge runs automatically on form submit without interrupting the user), and v3 score-based risk analysis (where every page load is silently assessed and a 0.0–1.0 confidence score is returned for the developer to act on). In all cases the integration pattern is the same: embed the Google JavaScript API on the front end to capture and attach a token, then call `(new ReCaptcha($secret))->verify($token, $ip)` on the back end and branch on `$resp->isSuccess()`. The library has zero production dependencies and requires only PHP ≥ 8.4 and network access to `www.google.com`. For robust production deployments the recommended pattern chains the fluent setters — `setExpectedHostname()` to prevent cross-domain token reuse, `setExpectedAction()` for v3 to detect action substitution, `setScoreThreshold(0.5)` as a baseline bot filter, and `setChallengeTimeout(300)` to prevent stale submissions — before calling `verify()`. The pluggable `RequestMethod` interface (with `CurlPost`, `Post`, and `SocketPost` built-in) means the library works in virtually any PHP hosting environment regardless of which HTTP functions are enabled, and can be unit-tested by injecting a custom mock transport.