Как добавить reCAPTCHA v3 в PHP-форму
Чтобы добавить reCAPTCHA v3 в PHP-форму, создайте ключи для домена, получите токен при отправке и проверьте его на сервере. Браузер передаст токен вместе с полями через Ajax, а PHP проверит его через API Google и решит, что делать с обращением.
В статье используются два файла – index.html и contact.php. В конце приведены полные листинги с проверкой email, токена, действия, домена и оценки.
- Почему Google reCAPTCHA v3?
- Регистрация ключей reCAPTCHA v3
- HTML-форма и подготовка файлов
- Подключение reCAPTCHA и отправка формы через Ajax
- Проверка токена в contact.php
- Полный обработчик contact.php
- Как подобрать порог score
- Как проверить работу формы
- Как скрыть значок reCAPTCHA
- Что сделать перед приемом сообщений
Почему Google reCAPTCHA v3?
reCAPTCHA v2 показывает флажок «Я не робот» и иногда просит решить задание. Пользователь должен выполнить это действие перед отправкой формы.

reCAPTCHA v3 работает в фоне и возвращает оценку score от 0,0 до 1,0. Чем выше оценка, тем менее подозрительным сервис считает действие.
Ваш сервер выбирает реакцию на оценку. Очевидный спам можно отклонить, пограничное обращение отправить на модерацию, а важную заявку дополнительно подтвердить. reCAPTCHA v3 снижает поток нежелательных отправок, но не доказывает, что посетитель – человек. Поэтому серверная проверка обязательна.
Описание клиентской части есть в документации reCAPTCHA v3, а формат проверки токена описан в документации server-side verification.
Регистрация ключей reCAPTCHA v3
Откройте google.com/recaptcha/admin и войдите в аккаунт Google. Для формы нужны публичный site key и секретный secret key.
- Откройте создание ключа для сайта. В поле «Метка» укажите понятное название, например
Форма обратной связи. По этой метке вы отличите ключ формы от других ключей. - Выберите тип reCAPTCHA v3. Консоль связывает ключ с проектом Google Cloud, поэтому выберите подходящий проект или создайте новый по подсказке формы.
- В поле «Домены» добавьте имя сайта без
https://, пути страницы и порта, напримерexample.com. Для рабочего сайта добавьте все домены, на которых будет работать форма. - Завершите регистрацию сайта. Для подключения сохраните пару ключей этого сайта. Ориентируйтесь на их обозначения:
site key– публичный ключ для HTML,secret key– секретный ключ для PHP.
Публичный ключ подставьте в адрес JavaScript API и константу SITE_KEY в index.html. Секретный ключ храните только на сервере: не помещайте его в HTML, JavaScript, публичный репозиторий или сообщения об ошибках.
HTML-форма и подготовка файлов
Создайте рядом два файла: index.html и contact.php. Первый содержит форму и JavaScript, второй принимает POST-запрос и проверяет токен.
Разместите файлы на сервере с PHP и расширением cURL. Открывайте страницу через HTTPS и домен, который добавили в список ключа.
Форма передает имя, email, сообщение и скрытое поле recaptcha_response. Атрибуты required и type="email" помогают браузеру показать ошибку, а PHP повторяет проверку для прямых запросов.
Подключение reCAPTCHA и отправка формы через Ajax
Получайте токен внутри обработчика submit. Так каждый запрос получает новый токен непосредственно перед отправкой данных.
Вариант ниже использует встроенный fetch и не требует jQuery. Замените оба вхождения YOUR_SITE_KEY_HERE ключом своего домена.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Форма с reCAPTCHA v3</title>
</head>
<body>
<form id="contact-form" action="contact.php" method="post">
<p>
<label for="name">Имя</label>
<input id="name" name="name" type="text"
autocomplete="name" maxlength="100" required>
</p>
<p>
<label for="email">Электронная почта</label>
<input id="email" name="email" type="email"
autocomplete="email" maxlength="254" required>
</p>
<p>
<label for="message">Сообщение</label>
<textarea id="message" name="message" rows="6"
maxlength="5000" required></textarea>
</p>
<input type="hidden" name="recaptcha_response"
id="recaptchaResponse">
<button id="submit-button" type="submit">Отправить</button>
<p id="status" role="status" aria-live="polite"></p>
</form>
<noscript>Для отправки формы включите JavaScript.</noscript>
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY_HERE"></script>
<script>
const SITE_KEY = 'YOUR_SITE_KEY_HERE';
const form = document.getElementById('contact-form');
const button = document.getElementById('submit-button');
const status = document.getElementById('status');
const tokenField = document.getElementById('recaptchaResponse');
let submitting = false;
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (submitting) return;
submitting = true;
button.disabled = true;
tokenField.value = '';
status.textContent = 'Проверяем форму…';
try {
if (typeof grecaptcha === 'undefined') {
throw new Error('Не удалось загрузить reCAPTCHA.');
}
await new Promise((resolve) => grecaptcha.ready(resolve));
const token = await grecaptcha.execute(SITE_KEY, {
action: 'contact'
});
if (!token) {
throw new Error('Не удалось получить токен reCAPTCHA.');
}
tokenField.value = token;
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: { 'Accept': 'application/json' }
});
const result = await response.json();
if (!response.ok || result.ok !== true) {
throw new Error(result.message || 'Сервер отклонил запрос.');
}
status.textContent = result.message;
} catch (error) {
status.textContent = error instanceof Error
? error.message
: 'Не удалось выполнить запрос. Попробуйте еще раз.';
} finally {
tokenField.value = '';
button.disabled = false;
submitting = false;
}
});
</script>
</body>
</html>Обработчик сначала отменяет обычную отправку через preventDefault(). Затем grecaptcha.execute получает токен для действия contact, скрытое поле сохраняет токен, а fetch отправляет форму через POST.
Блокировка кнопки не дает создать второй запрос во время первого. После ошибки обработчик очистит поле и получит новый токен при следующей попытке.
Проверка токена в contact.php
PHP отправляет настоящий POST-запрос к siteverify. Секрет и токен передаются в теле запроса, поэтому обработчик не кладет секретный ключ в URL.
Ответ API содержит несколько полей. Используйте их вместе: одного success недостаточно для проверки формы.
| Поле | Назначение | Проверка |
|---|---|---|
success | Токен прошел проверку | Ожидайте значение true |
score | Оценка действия от 0,0 до 1,0 | Сравните с порогом приложения |
action | Имя защищенного действия | Ожидайте contact |
hostname | Домен, где получен токен | Сравните с разрешенным списком |
challenge_ts | Временная метка проверки | Используйте для диагностики |
error-codes | Коды ошибок API | Записывайте в журнал без ключа и токена |
Обработчик должен отклонить запрос без токена, при ошибке cURL, неверном JSON или неполном ответе. Список разрешенных доменов задайте в коде, а не в POST-параметре и не в заголовке Host.
Полный обработчик contact.php
Сохраните код в файле contact.php без вывода перед открывающим PHP-тегом. Замените секретный ключ и значения $allowedHosts.
<?php
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
function respond($status, $ok, $message) {
http_response_code($status);
echo json_encode([
'ok' => $ok,
'message' => $message
], JSON_UNESCAPED_UNICODE);
exit;
}
function postString($key) {
$value = $_POST[$key] ?? '';
return is_string($value) ? trim($value) : '';
}
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
header('Allow: POST');
respond(405, false, 'Форма принимает только POST-запросы.');
}
$name = postString('name');
$email = postString('email');
$message = postString('message');
$token = postString('recaptcha_response');
if ($name === '' || $message === '' ||
filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
respond(422, false, 'Заполните имя, корректный email и сообщение.');
}
if (strlen($name) > 400 || strlen($email) > 254 ||
strlen($message) > 20000) {
respond(422, false, 'Одно из полей содержит слишком длинный текст.');
}
if ($token === '') {
respond(422, false, 'Нет токена reCAPTCHA. Отправьте форму еще раз.');
}
$secret = 'YOUR_SECRET_KEY_HERE';
$allowedHosts = ['example.com', 'www.example.com'];
$minimumScore = 0.5;
if ($secret === 'YOUR_SECRET_KEY_HERE' ||
!function_exists('curl_init')) {
error_log('Contact form: secret or cURL is not configured');
respond(503, false, 'Проверка формы временно недоступна.');
}
$curl = curl_init('https://www.google.com/recaptcha/api/siteverify');
if ($curl === false) {
respond(503, false, 'Проверка формы временно недоступна.');
}
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => $secret,
'response' => $token
], '', '&', PHP_QUERY_RFC3986),
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded'
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10
]);
$body = curl_exec($curl);
$curlError = curl_errno($curl);
$httpStatus = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($body === false || $curlError !== 0 || $httpStatus !== 200) {
error_log('reCAPTCHA transport error: ' . $curlError .
', HTTP: ' . $httpStatus);
respond(503, false, 'Проверка недоступна. Попробуйте позже.');
}
$verification = json_decode($body, true);
if (!is_array($verification) || json_last_error() !== JSON_ERROR_NONE) {
error_log('reCAPTCHA returned invalid JSON');
respond(503, false, 'Проверка недоступна. Попробуйте позже.');
}
$errorCodes = $verification['error-codes'] ?? [];
if (!is_array($errorCodes)) {
respond(503, false, 'Проверка недоступна. Попробуйте позже.');
}
if ($errorCodes !== []) {
error_log('reCAPTCHA error-codes: ' . json_encode($errorCodes));
if (in_array('timeout-or-duplicate', $errorCodes, true)) {
respond(422, false, 'Токен устарел или уже использован. Повторите отправку.');
}
respond(403, false, 'Не удалось пройти проверку reCAPTCHA.');
}
if (($verification['success'] ?? false) !== true) {
respond(403, false, 'Не удалось пройти проверку reCAPTCHA.');
}
if (($verification['action'] ?? '') !== 'contact') {
respond(403, false, 'Действие reCAPTCHA не совпадает с формой.');
}
$hostname = $verification['hostname'] ?? '';
if (!is_string($hostname) ||
!in_array(strtolower($hostname), $allowedHosts, true)) {
respond(403, false, 'Хост reCAPTCHA не разрешен.');
}
$score = $verification['score'] ?? null;
if ((!is_float($score) && !is_int($score)) ||
$score < 0 || $score > 1) {
respond(503, false, 'Сервис вернул некорректную оценку.');
}
if ($score < $minimumScore) {
respond(403, false, 'Отправка требует дополнительной проверки.');
}
// Здесь подключите сохранение или отправку сообщения.
respond(200, true, 'Проверка пройдена. Данные формы корректны.');Функция postString безопасно получает необязательное поле и не создает предупреждение при отсутствии ключа в $_POST. Функция filter_var проверяет формат email, а серверные ограничения длины не дают принять чрезмерный ввод.
Код не отключает проверку TLS-сертификата. Если cURL сообщает об ошибке сертификата, исправьте хранилище доверенных сертификатов сервера.
Храните секрет в конфигурации вне публичного файла. При ошибке API не разрешайте отправку в обход проверки: иначе сбой внешнего сервиса откроет обработчик прямым запросам.
Как подобрать порог score
Значение 0.5 подходит как стартовая настройка примера. Оно не отделяет всех людей от всех ботов, поэтому анализируйте статистику действия contact в консоли reCAPTCHA.
Сопоставляйте оценки с результатами модерации и содержимым обращений. Если реальные заявки часто получают низкий score, смягчите реакцию на пограничные значения.
- Очевидный спам отклоняйте без отправки письма.
- Пограничные оценки сохраняйте в очередь модерации.
- Для важных заявок добавьте подтверждение контакта или ручную проверку.
Порог меняйте в $minimumScore. При этом оставляйте проверки success, action и hostname: высокий score не исправляет неверный домен или чужое действие.
Как проверить работу формы
Откройте index.html по разрешенному домену, заполните поля и нажмите «Отправить». После успешных проверок появится сообщение «Проверка пройдена. Данные формы корректны.»
- Откройте инструменты разработчика браузера и вкладку сетевых запросов. В POST-запросе к
contact.phpполеrecaptcha_responseдолжно содержать токен. - Посмотрите JSON-ответ обработчика. Успешный ответ содержит
"ok": true, отказ –"ok": false. - Отправьте POST-запрос без токена. Сервер должен вернуть HTTP-код 422 и сообщение о пустом токене.
- Временно укажите в
$allowedHostsдругой домен. Запрос с токеном должен получить отказ проверки хоста. - Временно замените ожидаемое действие
contactнаother. Запрос должен завершиться отказом из-за несовпадения действия.
curl -i -X POST https://example.com/contact.php \
--data-urlencode "name=Проверка" \
--data-urlencode "email=test@example.com" \
--data-urlencode "message=Тест без токена"Код timeout-or-duplicate означает, что ответ больше недействителен: токен устарел или уже использован. Коды missing-input-secret и invalid-input-secret указывают на проблему с секретным ключом.
Если Google недоступен посетителям, API допускает запасной адрес www.recaptcha.net. Его можно использовать вместо www.google.com в клиентском и серверном адресах; рекомендация приведена в FAQ reCAPTCHA.
Как скрыть значок reCAPTCHA
После подключения API на странице появляется значок reCAPTCHA. Скрыть его можно при видимом брендинге reCAPTCHA в пользовательском потоке. Разместите рядом с формой текст со ссылками на политику конфиденциальности и условия Google. Оставьте его заметным для посетителя.
<p>
This site is protected by reCAPTCHA and the Google
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
</p>После этого добавьте CSS-правило в таблицу стилей страницы:
.grecaptcha-badge {
visibility: hidden;
}Что сделать перед приемом сообщений
Пример заканчивается проверкой формы и не отправляет письмо. Подключите сохранение или отправку сообщения после успешной проверки reCAPTCHA. Возвращайте ответ об успехе только после выполнения этой операции.
Ограничьте частоту запросов и размер POST-тела. При записи в базу используйте подготовленные запросы, а при выводе сообщения в HTML экранируйте пользовательский текст.
Совет от нашей редакции: сначала протестируйте отказ без токена и неверный hostname, а затем подключайте реальную отправку обращений.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- Как сделать редирект на PHP 6 мин чтения
- Основы ООП в PHP: принципы и примеры 6 мин чтения
- Адаптивный помесячный календарь на AJAX и PHP 2 мин чтения
- Как создать формы регистрации и авторизации на PHP 5 мин чтения
Комментарии
Добрый день, Вадим! Повторил код, однако вижу бесконечное сообщение "Processing..." Видимо данные формы обратной связи передаются в никуда. Это может быть результатом того что Вы в форме обратной связи не указали action=test.php, где test.php просто вывел бы данные самой формы. То есть я поддерживаю сообщения от Слава Г. и от Oleg G.
Всё бы классно, если бы еще обработчик php-mail написали ...
Здравствуйте, а где изменять email, куда запрос с формы придет??