# google/recaptcha: the small PHP library that checks Google's answer for you

> A single-purpose PHP client for the server side of reCAPTCHA verification, covering both the v2 checkbox and the v3 score APIs, with the transport and validation details a careful implementer needs.

**google/recaptcha** — PHP client library for reCAPTCHA, a free service to protect your website from spam and abuse.

- Repository: https://github.com/google/recaptcha
- Website: http://www.google.com/recaptcha/
- Stars: 3,577 · Forks: 771
- Language: PHP
- License: BSD-3-Clause
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/google-recaptcha

## What this repository does and what it leaves to Google

The scope here is narrower than the name suggests. `google/recaptcha` is a PHP client library, licensed BSD-3-Clause, whose entire job is the server-side verification step: your frontend collected a response token from the reCAPTCHA service, and this library forwards that token plus your secret key to Google and interprets the reply. It supports both the v2 API, where a human clicks a checkbox, and the v3 API, where JavaScript calls `grecaptcha.execute()` and returns a token without an obvious challenge to the user.

Everything else is elsewhere. Keys are created at the Google reCAPTCHA admin console, not in this repository. The widget markup, the script tag, and the integration guide live on the developer site. A hosted demo runs at `recaptcha-demo.appspot.com`, and the topic tags on the project are simply abuse, recaptcha, and spam. With 3,578 stars, 772 forks, and zero open issues against a default branch of `main`, this is one of the more settled third-party PHP packages you will install, and the last recorded push is dated 2026-09-23.

The library also draws an explicit boundary around reCAPTCHA Enterprise. A callout at the top of the README says Enterprise is supported through a different package entirely, the Google Cloud Recaptcha Enterprise for PHP client. If your credentials came from the Enterprise product, installing `google/recaptcha` gives you the wrong client for the job, and no amount of configuration on this side will fix that.

## Installing the client and choosing between the 2.0 and 1.x lines

Composer is the supported path, and the package name on Packagist is `google/recaptcha`. From your project directory:

```bash
composer require google/recaptcha "^2.0"
```

Or you can write the requirement into `composer.json` by hand and let the resolver work it out:

```json
"require": {
    "google/recaptcha": "^2.0"
}
```

The PHP requirement is the decision worth pausing on. Version 2.0 requires PHP 8.4 or newer. Support moved to PHP 8 with the 1.3 release, and the README points anyone still on an older runtime at the 1.2 releases. If you are on PHP 8.0 through 8.3, the compatibility line is `^1.5`, not `^2.0`. That distinction is easy to miss because both lines are actively maintained: 2.0.0, 1.5.2, and 1.5.1 were all published within about half an hour of each other on 2026-09-22, which reads oddly until you notice that 1.5.2 is a backport rather than a predecessor.

There is a direct download path too, for projects that do not use Composer. Grab the ZIP, extract it, and require the bundled autoloader:

```php
require_once '/path/to/recaptcha/src/autoload.php';
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
```

The classes follow PSR-4, so if you already run an autoloader of your own, requiring the individual files works just as well.

## Verifying a response and reading the error codes

The canonical usage is short enough to read in one go. Construct the `ReCaptcha` class with your secret key, chain on whatever validation rules apply, then call `verify()` with the response token and the user's IP address. The token usually arrives as `$_POST[\ReCaptcha\ReCaptcha::RESPONSE_KEY]`, or in the variable that `grecaptcha.execute()` returned on the JavaScript side.

```php
<?php
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha->setExpectedHostname('recaptcha-demo.appspot.com')
                  ->verify($gRecaptchaResponse, $remoteIp);
if ($resp->isSuccess()) {
    // Verified!
} else {
    $errors = $resp->getErrorCodes();
}
```

Failure is not an exception, which is the detail that shapes how you write the calling code. You check `isSuccess()` and, when it is false, collect error codes through `getErrorCodes()`. Those codes are constants on the `ReCaptcha` class, with names such as `ReCaptcha::E_HOSTNAME_MISMATCH`, so you can branch on them without hardcoding raw strings.

There are five `set` methods available, and each one returns the instance so you can chain. `setExpectedHostname()` is required when you have disabled Domain/Package Name Validation for your credentials, and the README flags an important limit: if you need to accept several hostnames, do not call this method, because it checks a single value. Verify first, then compare `getHostname()` against your own allow list. `setExpectedApkPackageName()` does the equivalent job for responses coming from an Android app. The remaining three, `setExpectedAction()`, `setScoreThreshold()`, and `setChallengeTimeout()`, all belong to the v3 path, and the last one covers the window between a user passing the challenge and your server getting around to processing it.

## Chaining setters versus cloning with the with-prefixed counterparts

Every `set` method has an immutable twin. `withExpectedHostname()`, `withExpectedApkPackageName()`, `withExpectedAction()`, `withScoreThreshold()`, and `withChallengeTimeout()` each return a cloned `ReCaptcha` rather than mutating the instance you called them on, and the clone comes back with the change applied.

```php
<?php
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha->setExpectedHostname('recaptcha-demo.appspot.com')
                  ->setExpectedAction('homepage')
                  ->setScoreThreshold(0.5)
                  ->verify($gRecaptchaResponse, $remoteIp);

if ($resp->isSuccess()) {
    // Verified!
} else {
    $errors = $resp->getErrorCodes();
}
```

The documented reason to prefer the `with` variants is lifetime. If a base instance lives in a dependency injection container or in a persistent worker runtime, mutating it per request leaks configuration from one request into the next. Build the shared base once and derive per-call copies from it:

```php
<?php
$baseRecaptcha = (new \ReCaptcha\ReCaptcha($secret))
    ->withExpectedHostname('recaptcha-demo.appspot.com')
    ->withChallengeTimeout(120);

$resp = $baseRecaptcha
    ->withExpectedAction('homepage')
    ->withScoreThreshold(0.5)
    ->verify($gRecaptchaResponse, $remoteIp);
```

The 2.0 release extended this thinking further, converting `Response` and `RequestParameters` into `readonly` classes with promoted constructor properties. A response object you get back from `verify()` can no longer be edited in place, which removes a class of shared-state bugs that were possible under the older mutable design.

## Which transport actually makes the request

Underneath the verification call is a `RequestMethod`, and since version 1.4.2 the default has been cURL. That path lives in `RequestMethod\CurlPost`. When the cURL extension is unavailable, the library falls back to `stream_context_create()` plus `file_get_contents()`, implemented in `RequestMethod\Post`. The README marks the behavior change as a note, since code written against earlier releases may have assumed the `file_get_contents()` path was always used.

If you need the old behavior deliberately, pass the transport as the second constructor argument:

```php
<?php
$recaptcha = new \ReCaptcha\ReCaptcha($secret, new \ReCaptcha\RequestMethod\Post());
```

The same constructor slot accepts any implementation of the `RequestMethod` interface, which is how you would plug in a custom HTTP client for a framework that already has one. The README's discussion of that injection point is the last section in the file and it cuts off mid sentence, so the external API documentation is where you would confirm the interface's full contract. A third transport exists in the tree as `SocketPost`, and `examples/recaptcha-request-socket.php` shows it in use.

Two hardening changes in the 2.0.0 release touch these paths directly: OpenSSL `verify_peer` and `verify_peer_name` are now enforced in `Post`, and both `CurlPost` and `Post` carry explicit 60-second request timeouts so a hung upstream call fails predictably instead of stalling a worker. `SocketPost` also gained HTTP/1.1 response status parsing. The 1.5.2 release backported the timeout and peer verification fixes onto the older API surface, and added `roave/backward-compatibility-check` to CI so future 1.x maintenance merges get checked for accidental signature changes.

## What the 1.5.1 rollback says about this project's release discipline

The release history is unusually instructive. Version 1.5.0 shipped breaking changes in a minor release: strict parameter and return type hints on public methods, `readonly` modifiers added to `Response` and `RequestParameters`, and the `RequestMethod\Curl` and `RequestMethod\Socket` wrapper classes removed. Version 1.5.1 reversed all of it, restoring untyped public signatures with PHPDoc annotations, stripping `readonly` so that subclasses and `PHPUnit::createMock()` test doubles would work, and putting the two wrapper classes back along with the legacy constructor parameter order.

The stated reason for the rollback is worth knowing if you write tests against this package. `readonly` properties interact badly with mocking: a double cannot be populated through a promoted constructor the way the real class expects, so the tests break before the code under test gets a chance to run.

That episode explains the shape of 2.0.0. Everything that broke compatibility in 1.5.0 is back, but on the correct major version, alongside `declare(strict_types=1)` across `ReCaptcha`, `Response`, `RequestParameters`, and `RequestMethod`, plus full PHP 8.5 compatibility. The transport constructors were also simplified, so `CurlPost` and `SocketPost` now accept a nullable `?string $siteVerifyUrl` directly rather than through intermediate wrapper objects. The README tells 1.x users who cannot absorb the breaking changes to stay on the `^1.5` line, where 1.5.2 preserves the full 1.4.2 public API. The `examples/` directory tracks the API split closely, with `recaptcha-v2-checkbox.php` and `recaptcha-v2-invisible.php` for the older pattern and `recaptcha-v3-immutable.php` for the `with` style, which makes it a reasonable way to see the difference in working code.

## Conclusion

google/recaptcha is a narrow library doing one job well: taking a token your browser produced, posting it to Google's verify endpoint, and handing you a Response object with an isSuccess flag and a list of error codes. There is no widget to configure here, no scoring model, and no dashboard, because all of that lives in the Google Cloud console rather than in this repository. The part that costs you the most reading time is the major version decision, since 2.0 requires PHP 8.4 and carries breaking API changes while the 1.5 line keeps the older signatures alive. Start with `examples/recaptcha-v2-checkbox.php` for the ordinary path, read `ARCHITECTURE.md` if you need to know why `RequestMethod` is an interface, and go to the Google Cloud Recaptcha Enterprise client instead of this one if your keys are Enterprise keys.

## FAQ

### Which PHP versions does the reCAPTCHA client library support?

Version 2.0 requires PHP 8.4 or newer. For PHP 8.0 through 8.3, use the `^1.5` compatibility line, and for anything older than PHP 8 the README directs you to the 1.2 releases.

### How do I install the reCAPTCHA PHP client without Composer?

Download the repository ZIP and require the bundled autoloader at `src/autoload.php`. The classes are PSR-4 compliant, so an autoloader you already maintain will pick them up as well.

### Why does verification fail when I set an expected hostname?

Calling `setExpectedHostname()` compares the response against a single hostname, so it fails whenever your site is reachable under more than one name. The README advises skipping that method and checking `getHostname()` against your own allow list after calling `verify()`.

### How do I handle reCAPTCHA v3 scores in PHP?

Use `setExpectedAction()` to pin the action name and `setScoreThreshold()` to set the score your application requires. A response below the threshold comes back with `isSuccess()` false and error codes from `getErrorCodes()`.

### Does this library work with reCAPTCHA Enterprise keys?

No. The README states that reCAPTCHA Enterprise is supported through the separate Google Cloud Recaptcha Enterprise for PHP client, not through this package.

## Sources

- [google/recaptcha on GitHub](https://github.com/google/recaptcha)
- [License: BSD-3-Clause](https://github.com/google/recaptcha/blob/main/LICENSE)
- [Project website](http://www.google.com/recaptcha/)
- [README](https://github.com/google/recaptcha/blob/main/README.md)
- [Releases](https://github.com/google/recaptcha/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/google-recaptcha
