Using Google reCAPTCHA v3 in PHP
Google reCAPTCHA v3 is invisible—no challenges, no image grids. It watches how users interact with the page and returns a score between 0.0 (bot) and 1.0 (human). Your server gets this score and decides what to do: accept, reject, or ask for more proof.
No friction for legitimate users, but you need to tune score thresholds for your traffic, and Google's JavaScript loads on every page. If you want to avoid Google's data collection or need GDPR compliance, use 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
There's no visible widget. Load the API script with your site key and call grecaptcha.execute() before the form submits. The token goes into a hidden input.
<!-- 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) labels requests in your reCAPTCHA admin console so you can tell which form generated which scores. Use descriptive values: 'contact', 'login', '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. Check both success and score—a successful verification with a low score still means the request looks like a bot.
<?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 main tuning knob for v3. Google recommends 0.5, but the right value depends on your traffic.
- 0.5 — Google's default. Good starting point for most forms.
- 0.7+ — Stricter. Catches more bots but also blocks more real users, especially those on slow connections, older devices, or privacy-focused browsers.
- 0.3 — Permissive. Lets borderline traffic through. Use this if your audience skews toward VPN users, Tor users, or people running aggressive ad blockers.
During development, log the raw score, not just pass/fail. After a few hundred submissions you'll see where legitimate users cluster. VPN users, privacy browsers (Brave, Firefox with uBlock), and people with limited Google history score lower even when human—if that's your audience, lower the threshold or add a fallback challenge instead of blocking them.
cURL Alternative
If allow_url_fopen is disabled on your host, use cURL. The endpoint accepts POST 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 probably being called with a wrong or missing site key. Open the browser's network tab, find the request towww.google.com/recaptcha/api2/userverify, and confirm the key matches your console registration. - "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: v3 tokens last two minutes. If the user sits on the page longer than that, the token goes stale. Generate a fresh token right before submission, as the example above does—not on page load.
- Legitimate users failing: Lower the threshold or add a fallback (email verification, a v2 challenge) for submissions scoring between 0.3 and your threshold. Don't hard-block them.
Privacy Note
reCAPTCHA v3 is surveillance infrastructure. Google's JavaScript loads on every page, and Google receives interaction signals from your users. Under GDPR, this requires privacy policy disclosure and likely cookie consent. If your users are in the EU or privacy matters to you, review the CAPTCHA alternatives comparison or switch to Cloudflare Turnstile, which does not feed data into an advertising network.