Google reCAPTCHA v3
formsub asks Google for a token on submit and appends it as recaptcha_response. Only your server can decide if the token is valid. The client never talks to the secret key.
Front end
- Load the reCAPTCHA v3 script so
grecaptchaexists:
html
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>- Enable it in
newForm:
js
formsub.newForm({
formId: 'contact-form',
submitButton: '#submit-button',
ajaxUrl: '/api/contact',
recaptcha: true,
recaptcha_site_key: 'YOUR_SITE_KEY',
});If grecaptcha is missing at submit time, formsub treats that as a failure and does not send the form.
The token is single-use and expires after about two minutes. formsub requests it with action: "submit" — your server check must use the same action string.
What you receive
recaptcha_response=03AGdBq24...long-token...
email=user@example.com
name=JaneVerify with Google
Use the siteverify API. POST:
| Parameter | Value |
|---|---|
secret | Your reCAPTCHA secret key (never in front-end code) |
response | The recaptcha_response field from the form |
A successful Google reply looks like:
json
{
"success": true,
"score": 0.9,
"action": "submit",
"challenge_ts": "2026-08-03T08:00:00Z",
"hostname": "example.com"
}Accept the form only when:
success === trueaction === "submit"scoremeets your threshold (start around>= 0.5and tune)
Otherwise return a formsub error payload.
PHP (vanilla)
Store the secret outside the web root or in an environment variable:
php
<?php
// config.php — do not commit this file
define('RECAPTCHA_SECRET', 'YOUR_SECRET_KEY');Helper:
php
<?php
function verify_recaptcha(string $token): array
{
$response = file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'secret' => RECAPTCHA_SECRET,
'response' => $token,
]),
],
]));
return json_decode($response, true) ?: ['success' => false];
}At the start of the form handler:
php
<?php
require_once 'config.php';
$token = $_POST['recaptcha_response'] ?? '';
$result = verify_recaptcha($token);
if (
empty($result['success']) ||
($result['action'] ?? '') !== 'submit' ||
($result['score'] ?? 0) < 0.5
) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'code' => 400,
'message' => 'reCAPTCHA verification failed.',
]);
exit;
}
// continue with form processing...TIP
Prefer curl or Guzzle in production if allow_url_fopen is disabled.
WordPress (PHP)
In wp-config.php (above “That's all, stop editing!”):
php
define('RECAPTCHA_SECRET', 'YOUR_SECRET_KEY');Helper:
php
<?php
function myplugin_verify_recaptcha(string $token): array
{
$response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
'body' => [
'secret' => RECAPTCHA_SECRET,
'response' => $token,
],
]);
if (is_wp_error($response)) {
return ['success' => false];
}
return json_decode(wp_remote_retrieve_body($response), true) ?: ['success' => false];
}In your REST route, admin-ajax.php handler, or custom endpoint:
php
<?php
$token = $_POST['recaptcha_response'] ?? '';
$result = myplugin_verify_recaptcha($token);
if (
empty($result['success']) ||
($result['action'] ?? '') !== 'submit' ||
($result['score'] ?? 0) < 0.5
) {
wp_send_json_error([
'status' => 'error',
'code' => 400,
'message' => 'reCAPTCHA verification failed.',
], 400);
}
// continue with form processing...Return JSON in the shape formsub expects so the client can show success or error copy.