Using Google reCAPTCHA v3 in PHP
Google reCAPTCHA v3 is an invisible bot detection system — there are no challenges, no image grids, and no user interaction required. Instead, it monitors signals across the user's session and returns a score between 0.0 (almost certainly a bot) and 1.0 (almost certainly human). Your server receives this score and decides what to do: accept, reject, or trigger a secondary verification step.
Because it runs silently in the background, reCAPTCHA v3 introduces no friction for legitimate users. The trade-off is that you need to calibrate score thresholds for your traffic, and Google's JavaScript loads on every page that uses it. If GDPR compliance or avoiding Google data collection is a priority for your project, see the Cloudflare Turnstile guide instead.
Prerequisites
- A Google account
- Register your site at google.com/recaptcha/admin — select reCAPTCHA v3 and add your domain
- Copy your site key (public, used in the browser) and secret key (private, used on the server)
- PHP 7.2+ with
allow_url_fopenenabled or cURL available
Front-End Setup
Unlike v2, reCAPTCHA v3 doesn't render a visible widget. You load the API script with your site key and call grecaptcha.execute() just before the form submits. The resulting token is written into a hidden input and submitted with the form.
<!-- Load the reCAPTCHA v3 API with your site key -->
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
<form method="post" action="/contact" id="contact-form">
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="Your email" />
<!-- Hidden field to hold the reCAPTCHA token -->
<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" />
<button type="submit">Send</button>
</form>
<script>
document.getElementById('contact-form').addEventListener('submit', function (e) {
e.preventDefault();
const form = this;
grecaptcha.ready(function () {
grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' }).then(function (token) {
document.getElementById('g-recaptcha-response').value = token;
form.submit();
});
});
});
</script>
The action string ('submit' above) is a label that appears in your reCAPTCHA admin console, making it easier to distinguish scores from different forms on the same site. Use descriptive values like 'contact', 'login', or 'checkout'.
Server-Side Verification in PHP
Send the token to Google's siteverify endpoint via GET or POST. The response includes success, score, and action. Always check both success and score — a successful verification with a low score still indicates bot-like behaviour.
<?php
/**
* Verify a reCAPTCHA v3 token.
*
* @param string $token The g-recaptcha-response value
* @param string $secretKey Your reCAPTCHA secret key
* @param float $threshold Minimum score to accept (0.0–1.0, default 0.5)
* @return bool
*/
function verifyRecaptchaV3(string $token, string $secretKey, float $threshold = 0.5): bool
{
if (empty($token)) {
return false;
}
$url = 'https://www.google.com/recaptcha/api/siteverify?' . http_build_query([
'secret' => $secretKey,
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]);
$response = @file_get_contents($url);
if ($response === false) {
return false; // network error
}
$data = json_decode($response, true);
return
($data['success'] ?? false) === true &&
($data['score'] ?? 0.0) >= $threshold;
}
// Usage in your form processor:
$token = $_POST['g-recaptcha-response'] ?? '';
if (!verifyRecaptchaV3($token, 'YOUR_SECRET_KEY', 0.5)) {
http_response_code(400);
exit('Bot check failed or low confidence score.');
}
// Proceed with form handling
Understanding Score Thresholds
The $threshold parameter is the most important tuning knob in a v3 integration. Google recommends starting at 0.5, but the right value depends entirely on your traffic.
- 0.5 — Google's recommended default. A reasonable starting point for most forms.
- 0.7 and above — Stricter. Reduces bot traffic more aggressively, but increases the chance of blocking legitimate users, particularly those on slow connections, older devices, or privacy-focused browsers.
- 0.3 — Permissive. Allows borderline traffic through. Useful when your audience consists of users who consistently score lower (VPN users, Tor users, users with JavaScript disabled elsewhere on their browser).
During development, log the raw score rather than just the pass/fail result. After observing a few hundred real submissions, you'll have a clear picture of where your legitimate users cluster and can set the threshold accordingly. Users on VPNs, privacy browsers (Brave, Firefox with uBlock), or with limited browsing history on Google properties tend to score lower even when human — if your audience skews toward these groups, lower the threshold or add a fallback challenge rather than blocking them outright.
cURL Alternative
If allow_url_fopen is disabled on your host, use cURL. The endpoint accepts a POST request as well as GET.
<?php
function verifyRecaptchaV3Curl(string $token, string $secretKey, float $threshold = 0.5): bool
{
if (empty($token)) {
return false;
}
$ch = curl_init('https://www.google.com/recaptcha/api/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => $secretKey,
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$response = curl_exec($ch);
curl_close($ch);
if (!$response) {
return false;
}
$data = json_decode($response, true);
return
($data['success'] ?? false) === true &&
($data['score'] ?? 0.0) >= $threshold;
}
Troubleshooting
- Score is always 0.0:
grecaptcha.execute()is likely being called with an incorrect or missing site key. Open the browser's network tab, find the request towww.google.com/recaptcha/api2/userverify, and confirm the key matches what's registered in the console. - "invalid-input-secret": The secret key sent from PHP is wrong. Check for leading or trailing whitespace when reading it from a config file or environment variable.
- Token expired: reCAPTCHA v3 tokens are valid for two minutes. If the user spends a long time on the page before submitting, the token may be stale. Generate a fresh token immediately before submission, as the example above does — don't generate it on page load.
- Legitimate users failing: Lower the threshold or add a fallback (email verification, a simple v2 reCAPTCHA challenge) for submissions that score between 0.3 and your threshold, rather than hard-blocking them.
Privacy Note
reCAPTCHA v3 loads Google's JavaScript on every page where it runs, and Google receives user interaction signals from those pages. Under GDPR and similar regulations, this typically requires disclosure in your privacy policy and, depending on interpretation, a cookie consent mechanism. If your users are in the EU and privacy compliance is a concern, review the CAPTCHA alternatives comparison or consider switching to Cloudflare Turnstile, which does not feed data into an advertising network.