Замена Google reCAPTCHA на Яндекс SmartCaptcha в WordPress — пошагово
Почему reCAPTCHA на российском сайте — повод разобраться
Три причины посмотреть в сторону другого виджета.
Обработка данных требует отдельной оценки. reCAPTCHA собирает IP, отпечаток браузера, движения мыши и тайминги кликов. Для сайта с российской аудиторией это повод проверить, какие данные уходят в Google, на каком правовом основании, отражено ли это в политике и в уведомлении РКН. Само по себе наличие reCAPTCHA не делает сайт нарушителем: вывод зависит от фактических потоков данных и применимых требований 152-ФЗ. Разбор требований — в статье про запрет reCAPTCHA и Google Analytics.
Внешний виджет зависит от доступности Google. Скорость загрузки и сама возможность получить виджет зависят от сети, оператора, браузера и настроек блокировки. Гарантий нет: в одной сети всё работает, в другой скрипт не доходит.
Сломанная капча не защищает. Если скрипт не загрузился, форма либо не отправляется, либо уходит без проверки — зависит от обработчика. Оба варианта плохие.
Что понадобится
- Аккаунт в Yandex Cloud и платёжный аккаунт в статусе
ACTIVEилиTRIAL_ACTIVE. Без этого SmartCaptcha работает в ограниченном режиме: все проверки возвращаютok, то есть защиты нет. - Домен в списке сайтов капчи. Указывайте все хосты, на которых живёт сайт:
de-bor.ruиwww.de-bor.ru— это два разных хоста. - Доступ к файлам темы или к плагину, который отвечает за формы.
По деньгам: 10 000 успешных проверок в месяц бесплатно. Дальше 109,8 ₽ за 1000 запросов до 100 000 в месяц, свыше — 76,86 ₽ за 1000. Цены указаны на 28 октября 2026 года и могут меняться. Срок жизни токена и тарификация — разные условия: токен действует 5 минут и проверяется один раз, а успешный запрос по правилам тарификации учитывается, если уникальный токен выдан не более 10 минут назад. Просроченный токен проверку не проходит и не тарифицируется. Цены и условия — в правилах тарификации, сверяйте их перед расчётом бюджета.
Если платёжный аккаунт не активен, на странице появится плашка «Работа Yandex SmartCaptcha на странице ограничена», а API будет отвечать
ok на любой запрос. Виджет при этом может выглядеть совершенно нормально. В ограниченном режиме запросы не тарифицируются, но и защиты нет: проверяйте не картинку, а статус аккаунта и реальный ответ /validate.Шаг 1. Создать капчу и получить ключи
В консоли Yandex Cloud выберите каталог, откройте сервис Yandex SmartCaptcha и нажмите «Создать капчу». Заполните три поля:
- Имя — любое, например
de-bor-forms. - Список сайтов — домены без протокола и без слэша:
de-bor.ru,www.de-bor.ru. - Сложность — «Средняя» подходит большинству сайтов. Максимальная злит людей на медленном интернете.
После создания откройте капчу и скопируйте два ключа:
- Ключ клиента — публичный, идёт в HTML и JS.
- Ключ сервера — секретный, только в PHP или в переменной окружения. В браузер его отдавать нельзя.
Ключ сервера храните не в теме, а в wp-config.php или в .env за пределами вебрута. Тема обновляется, файл с ключом при этом не должен уехать вместе с ней.
// wp-config.php
define('SMARTCAPTCHA_SERVER_KEY', 'ysc2_ваш_ключ_сервера');
Шаг 2. Поставить виджет на страницу
Есть два метода подключения. Автоматический проще, расширенный нужен для невидимой капчи и для форм, которые отправляются через AJAX.
Автоматический метод
Скрипт сам находит все div с классом smart-captcha и рисует в них виджет.
<script src="https://smartcaptcha.cloud.yandex.ru/captcha.js" defer></script>
<div class="smart-captcha"
data-sitekey="ysc1_ваш_ключ_клиента"
style="height:100px"></div>
Высоту 100px документация рекомендует задавать заранее, чтобы страница не прыгала, когда виджет загрузится.
После прохождения проверки SmartCaptcha сама добавит внутрь контейнера скрытое поле:
<input type="hidden" name="smart-token" value="токен">
На сервере токен читается как $_POST['smart-token']. Если форма собирается вручную, поле попадёт в отправку само — это обычный input внутри формы.
Расширенный метод и невидимая капча
Кнопки «Я не робот» нет, проверка запускается по вашему событию — например, по клику на «Отправить». Подходит для форм, где лишний клик мешает конверсии.
<form id="lead-form">
<input type="hidden" id="smart-token" name="smart-token" value="">
<button type="submit">Отправить</button>
</form>
<div id="captcha-container"></div>
<script src="https://smartcaptcha.cloud.yandex.ru/captcha.js?render=onload&onload=onSmartCaptcha"></script>
<script>
var form = document.getElementById('lead-form');
var tokenInput = document.getElementById('smart-token');
var widgetId = null;
var captchaPending = false; // проверка уже запущена
var captchaVerified = false; // токен получен, можно отправлять
function onSmartCaptcha() {
if (!window.smartCaptcha) return;
widgetId = window.smartCaptcha.render('captcha-container', {
sitekey: 'ysc1_ваш_ключ_клиента',
invisible: true,
callback: function (token) {
captchaPending = false;
captchaVerified = true;
tokenInput.value = token;
form.requestSubmit();
}
});
window.smartCaptcha.subscribe(widgetId, 'network-error', function () {
captchaPending = false; // разрешаем повторить отправку
});
}
form.addEventListener('submit', function (e) {
if (!window.smartCaptcha) return; // скрипт не загрузился — решает сервер
// Первый проход: токена нет, запускаем проверку
if (!captchaVerified) {
e.preventDefault();
if (!captchaPending) {
captchaPending = true;
window.smartCaptcha.execute(widgetId);
}
return;
}
// Токен уходит в запросе. Готовим форму к следующей попытке:
// одноразовый токен уже израсходован.
setTimeout(function () {
captchaVerified = false;
captchaPending = false;
tokenInput.value = '';
window.smartCaptcha.reset(widgetId);
}, 0);
});
</script>
Скрипт размещён после формы, поэтому обработчики успевают найти элементы. Флаг captchaPending не даёт запустить execute() дважды, а captchaVerified живёт ровно один проход: как только отправка началась, состояние сбрасывается и следующая попытка требует нового токена. Сброс в setTimeout нужен, чтобы не очистить скрытое поле до того, как браузер соберёт данные формы. Если виджет сообщает сетевую ошибку, captchaPending снимается и пользователь может нажать «Отправить» ещё раз; автоматического восстановления нет, проверку нужно пройти заново.
При отключённом JavaScript виджет не загрузится, и на сервер уйдёт пустой токен. Такую отправку отклонит проверка из шага 3, если только вы явно не включили fail-open для сбоя сервиса.
Почему не form.submit(): он пропускает обработчики submit и валидацию. requestSubmit() проходит их штатно, а флаг не даёт обработчику запустить капчу по кругу. Если после ответа сервера страница осталась открытой (AJAX или ошибка), форма уже готова к новой проверке.
Если форм на странице несколько, храните widgetId от каждой и вызывайте execute(widgetId) и reset(widgetId) с нужным идентификатором. Без аргумента SmartCaptcha работает с первым отрисованным виджетом.
И ещё: по умолчанию в правом нижнем углу появляется блок со ссылкой на уведомление об обработке данных. Скрывать его (hideShield: true) можно только если вы уведомили пользователя другим способом — например, строкой в форме и пунктом в политике конфиденциальности.
В примерах из интернета и в старых статьях встречается
smartcaptcha.yandexcloud.net. Актуальная документация Яндекса указывает smartcaptcha.cloud.yandex.ru — и для скрипта, и для проверки токена. Если скопировать старый код, капча просто не загрузится.Шаг 3. Проверить токен на сервере
Токен одноразовый, живёт 5 минут и проверяется POST-запросом на https://smartcaptcha.cloud.yandex.ru/validate в формате x-www-form-urlencoded.
function check_smartcaptcha(string $token, string $ip, array $allowedHosts): ?bool
{
if ($token === '') {
return false; // токена нет — проверка не пройдена
}
if (! $allowedHosts) {
error_log('SmartCaptcha: не задан список разрешённых хостов');
return null;
}
$response = wp_remote_post('https://smartcaptcha.cloud.yandex.ru/validate', [
'timeout' => 2,
'body' => [
'secret' => SMARTCAPTCHA_SERVER_KEY,
'token' => $token,
'ip' => $ip,
],
]);
if (is_wp_error($response)) {
error_log('SmartCaptcha: ' . $response->get_error_message());
return null;
}
$code = wp_remote_retrieve_response_code($response);
if ($code !== 200) {
error_log('SmartCaptcha: HTTP ' . $code);
return null;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (! is_array($data) || ! isset($data['status'])) {
error_log('SmartCaptcha: некорректный JSON');
return null;
}
if ($data['status'] !== 'ok') {
return false; // токен отклонён
}
$host = strtolower(explode(':', (string) ($data['host'] ?? ''))[0]);
if ($host === '') {
error_log('SmartCaptcha: пустой host');
return null; // сервис не подтвердил домен
}
$allowed = array_map(static fn($h) => strtolower($h), $allowedHosts);
if (! in_array($host, $allowed, true)) {
error_log("SmartCaptcha: host '{$host}' не разрешён");
return false;
}
return true;
}
Функция использует wp_remote_post() и остальные функции HTTP API WordPress, поэтому отдельное расширение cURL не требуется. Ключ остаётся на сервере, в HTML и JS он не попадает.
Функция возвращает три состояния: true — проверка пройдена (статус ok и домен в списке разрешённых), false — проверка не пройдена, null — проверить не удалось. Это важнее, чем кажется: от null зависит, что делать с заявкой.
Разбор ответа простой. status: ok — сервис пропустил запрос, failed с пустым message — не пропустил, failed с текстом вроде Invalid or expired Token — проблема с токеном: его уже использовали, он протух или не пришёл. В обоих случаях это отказ проверки, а не доказательство, что перед вами бот: причиной может быть и неверный серверный ключ. Поле message нужно только для логов, сравнивать его в коде нельзя. Пустой host при status: ok считается неопределённым результатом: домен не подтверждён, поэтому функция возвращает null.
Fail-open или fail-closed
Когда API недоступен, есть два варианта, и выбирать его нужно осознанно. Политику задаём константой, например в wp-config.php:
// wp-config.php
define('SMARTCAPTCHA_FAIL_OPEN', true); // true — пропускать заявки при сбое проверки
Дальше — фрагмент логики. Техническая ошибка (null) зависит от политики, подтверждённый отказ (false) блокируется всегда. Этот фрагмент нужно вызвать внутри конкретного обработчика формы, до отправки письма или создания заказа.
$token = $_POST['smart-token'] ?? '';
$result = check_smartcaptcha(
$token,
$_SERVER['REMOTE_ADDR'] ?? '',
['de-bor.ru', 'www.de-bor.ru']
);
if ($result === null) {
// Сервис недоступен или ответ не разобрать
$isSpam = ! SMARTCAPTCHA_FAIL_OPEN;
error_log('SmartCaptcha недоступна: ' . ($isSpam ? 'заявка отклонена' : 'заявка пропущена'));
} else {
// Подтверждённый отказ блокируем при любой политике
$isSpam = ! $result;
}
Не размещайте такой код с безусловным
wp_die() или exit на верхнем уровне functions.php: он выполнится на каждом запросе и уронит сайт. Проверка должна вызываться только внутри обработчика конкретной формы. Для Contact Form 7 это фильтр wpcf7_spam — пример в шаге 4.Fail-open (true) соответствует рекомендации Яндекса не задерживать пользовательские запросы при HTTP-ошибках. Минус очевиден: пока сервис или сеть лежат, формы принимают всех подряд. Для сайта услуг это обычно меньшая потеря, чем остановка заявок, но факт пропуска нужно логировать и хотя бы раз в сутки просматривать.
Fail-closed (false) надёжнее с точки зрения защиты, но превращает любой сбой Яндекса в отказ формы. Для интернет-магазина с потоком заказов это риск потерять больше, чем от спама. Если выбираете этот вариант, добавьте понятное сообщение пользователю и отдельный канал связи.
Про host. При успешной проверке API возвращает домен, на котором пройдена капча; порт, если он есть, отбрасываем. Если передать список разрешённых хостов, несовпадение или пустой host дают false — проверка не пройдена. Это дополнительная проверка домена, а не гарантия защиты: она не заменяет контроль статуса платёжного аккаунта и не доказывает, что бот не пройдёт капчу. Указывайте все хосты, которые реально обслуживает сайт: de-bor.ru и www.de-bor.ru — разные значения.
Теперь про IP. В $_SERVER['REMOTE_ADDR'] лежит адрес посетителя, но если сайт за Cloudflare или другим прокси — там будет адрес прокси. Тогда берите HTTP_CF_CONNECTING_IP и только при реально включённом Cloudflare: этот заголовок подделывается, если запрос идёт напрямую. Проверить просто: отправьте форму и посмотрите, что уходит в лог.
Отправьте форму с пустым
smart-token. Сервер должен вернуть failed, а форма — не отправиться. Если письмо ушло, значит проверка не подключена или стоит после отправки.Шаг 4. Contact Form 7
Для стандартной формы CF7 проще готовый плагин Captcha by Yandex for Contact Form 7. Карточка WordPress.org: версия 1.2.7, 3000+ активных установок, PHP 7.4+, тестировался с WordPress 7.0.7. Ставится из админки, ключи вводятся на странице интеграции, в форму добавляется тег капчи. Код писать не нужно.
Плагин не тестировался с каждой комбинацией версий CF7 и WordPress. Перед установкой на боевой сайт прогоните его на копии: отправьте форму, проверьте сообщение об успехе и повторную отправку.
Второй путь — свой хук, без плагина. Сначала подключите скрипт на странице с формой — один раз, через тему. Например, в functions.php дочерней темы:
add_action('wp_enqueue_scripts', function () {
if (! is_page('kontakty')) {
return;
}
wp_enqueue_script(
'smartcaptcha',
'https://smartcaptcha.cloud.yandex.ru/captcha.js',
[],
null,
true
);
});
Замените kontakty на slug страницы с формой. В шаблон формы CF7 добавьте только контейнер — поле «Форма» принимает HTML:
<div class="smart-captcha"
data-sitekey="ysc1_ваш_ключ_клиента"
style="height:100px"></div>
Контейнер должен находиться внутри <form>, иначе скрытое поле smart-token не попадёт в отправку. В редакторе CF7 форма уже обёрнута в <form>, поэтому блок достаточно вставить в тело формы. Скрипт в теле формы не нужен: он подключён через тему и не загрузится дважды.
Дальше вешаем проверку на фильтр wpcf7_spam. В CF7 6.2.x он вызывается в конце метода проверки спама, после проверок длины User-Agent и nonce. Другие модули защиты подключаются к этому же фильтру, поэтому порядок задаёт приоритет: у нас 9, то есть раньше модулей с приоритетом 10.
add_filter('wpcf7_spam', 'deb_cf7_smartcaptcha', 9, 2);
function deb_cf7_smartcaptcha($spam, $submission)
{
if ($spam) {
return $spam; // уже поймали другой проверкой
}
// Формы, где стоит виджет. Укажите свои ID.
$form_ids = [123];
$form_id = $submission->get_contact_form()->id();
if (! in_array($form_id, $form_ids, true)) {
return $spam; // чужие формы не трогаем
}
$token = isset($_POST['smart-token']) ? (string) $_POST['smart-token'] : '';
if ($token === '') {
return true; // токена нет — не пропускаем
}
$result = check_smartcaptcha(
$token,
$_SERVER['REMOTE_ADDR'] ?? '',
['de-bor.ru', 'www.de-bor.ru']
);
if ($result === null) {
// Техническая ошибка: решает политика из wp-config.php
return ! SMARTCAPTCHA_FAIL_OPEN;
}
return ! $result;
}
Пустой токен блокируется при любой политике: это не сбой сервиса, а отсутствие проверки. При SMARTCAPTCHA_FAIL_OPEN = true недоступность API вернёт false — письмо уйдёт, при false — true, и CF7 пометит отправку спамом.
Замените [123] на ID своих форм: в админке CF7 ID виден в списке контактных форм. Что проверить на сайте: форма не уходит с пустым токеном, повторная отправка с тем же токеном получает отказ, при недоступности API срабатывает выбранная политика. У хука есть ограничения: фильтр не выполняется, если проверка спама пропускается самим CF7 — например, когда другой код включил wpcf7_skip_spam_check или форма настроена как subscribers_only и у пользователя есть право wpcf7_submit. Для обычной публичной формы это не проблема, но как на единственный барьер на хуке держаться не стоит.
После успешной отправки CF7 очищает форму, а виджет остаётся с использованным токеном. Следующая отправка получит Invalid or expired Token. Сбрасываем виджет по событию wpcf7submit — оно срабатывает после ответа сервера:
document.addEventListener('wpcf7submit', function (event) {
if (window.smartCaptcha) {
window.smartCaptcha.reset();
}
});
wpcf7submit приходит с event.detail.status — mail_sent, mail_failed, spam, validation_failed и другими. Сбрасывать можно после любого статуса: к этому моменту серверный запрос уже завершён, а невидимая капча при повторной попытке пройдёт проверку заново. Если форм несколько, reset() без аргумента сбросит только первый отрисованный виджет — сохраняйте widgetId и передавайте его явно.
Ту же логику можно прикрутить к WPForms и Elementor Forms — через их хуки валидации. Принцип один: вытащить токен из $_POST, проверить, вернуть ошибку до отправки письма. Точка интеграции зависит от версии плагина и типа формы, готового универсального хука нет. Для WooCommerce проверку встраивают в валидацию до создания заказа, например через woocommerce_checkout_process; для блочного чекаута и кастомных полей это может быть другая точка. Комментарии — отдельный сценарий, который требует собственной интеграции: проверку нужно ставить до записи комментария в базу, а не после.
Шаг 5. Проверить, что всё работает
- Откройте страницу с формой в инкогнито. Капча должна отрисоваться, а в HTML — появиться скрытое поле
smart-token. - В расширенном методе включите в параметрах
renderфлагtest: true— тогда задание показывается всегда. Это режим отладки виджета, на проде его нужно выключить. - Отправьте форму руками. В логе PHP должно появиться, что
/validateвернулok. - Отправьте форму курлом без токена — письмо не должно уйти, в ответе CF7 будет статус
spam. - Откройте DevTools → Network и убедитесь, что на странице нет запросов к
google.com/recaptchaиgstatic.com.
Если форма не отправляется вообще, смотрите консоль браузера: чаще всего это CSP, который запрещает smartcaptcha.cloud.yandex.ru, или кэш страницы, где остался старый виджет.
Отдельно убедитесь, что запрос к /validate действительно уходит с сервера. Поставьте временный error_log() перед вызовом функции или посмотрите исходящие запросы на стороне хостинга. Если записей нет, проблема в интеграции, а не в капче: некоторые хостеры и файрволы блокируют исходящие запросы к незнакомым доменам.
Где обычно ломается
Плашка «Работа Yandex SmartCaptcha ограничена». Платёжный аккаунт не в статусе ACTIVE или TRIAL_ACTIVE. API при этом отвечает ok на всё — форма отправляется, спам проходит. Внешне виджет выглядит рабочим, поэтому проверяйте биллинг и ответ /validate, а не картинку.
Invalid or expired Token. Токен проверили дважды. Типичные причины: двойная отправка формы, кнопка без блокировки, между открытием страницы и отправкой прошло больше 5 минут.
Капча не появляется. Домен не добавлен в список сайтов капчи, скрипт заблокирован CSP или рекламным блокировщиком. Ещё вариант — JS подключён с async без onload, и window.smartCaptcha ещё не существует в момент вызова.
Капча ломается на кэшируемой странице. Токен создаётся виджетом после проверки и в кэшируемом HTML его быть не должно, поэтому отключать кэш всех страниц с формами из-за SmartCaptcha не нужно. Если проблема есть, смотрите по порядку: не откладывает ли оптимизатор загрузку captcha.js, не инициализируется ли виджет дважды, не остаётся ли в форме токен от предыдущей попытки. Отключайте кэш для конкретной страницы только при подтверждённой несовместимости.
Несколько защит сразу. Если на сайте уже стоит Cloudflare Turnstile или JS-челлендж Cloudflare, пользователь может получить две проверки подряд. Это зависит от настроек: где-то челлендж срабатывает редко, где-то на каждом заходе. Посмотрите на путь пользователя и оставьте одну защиту, если вторая не даёт ощутимой пользы.
Когда SmartCaptcha не нужна
Заявок мало. Если форма отправляется несколько раз в день, капча может добавить трения больше, чем пользы. Это ориентир, а не правило: считайте по своим данным и тестируйте на реальном трафике.
Аудитория вне РФ. Если значительная часть посетителей из других стран, SmartCaptcha может показывать задания чаще, чем хотелось бы. Тогда разумнее hCaptcha или Turnstile. Юридическую оценку в этом случае нужно делать отдельно: сам выбор капчи не определяет соответствие сайта 152-ФЗ.
Форма внутри мобильного приложения. Для WebView у виджета есть параметр webview: true. Без него оценка пользователя менее точная, и капча может срабатывать на живых людях.
И ещё: капча не заменяет защиту от брутфорса. Для wp-login.php нужны ограничение попыток и двухфакторная аутентификация — SmartCaptcha там только дополнение. Honeypot, лимит по IP и CSRF-токен тоже не заменяют друг друга и не дают гарантии: каждый закрывает свой класс атак, и только вместе они заметно снижают спам.
FAQ
Нужно заменить капчу и проверить формы?
Уберу reCAPTCHA, подключу Яндекс SmartCaptcha к формам и проверю, что заявки не теряются. С кодом, логами и без лишних плагинов.