Дубли в Contact Form 7 обычно появляются не из-за одной причины, а из-за набора мелких сбоев: пользователь нажал кнопку дважды, браузер повторил запрос после ошибки сети, форма отправилась повторно из-за автосабмита, а на стороне сайта нет проверки на уже обработанный запрос. Если форма уходит в CRM, почту и вебхук одновременно, один дубль быстро превращается в несколько одинаковых лидов.
Ниже разберу рабочую схему: как понять, откуда берутся повторы, как закрыть их на уровне фронтенда и сервера, и как проверить, что защита действительно работает.
Когда проблема точно в дублях, а не в почте
Сначала важно не перепутать дубли с другой типичной проблемой — когда письмо не дошло, а пользователь отправил форму еще раз. В этом сценарии в админке, CRM или логах видно несколько одинаковых событий с разницей в секунды. Если же письмо одно, а форма в браузере показывает ошибку, это уже другая задача.
Признаки повторной отправки
- в CRM появляются одинаковые заявки с одинаковым временем;
- в логах вебхука видно два POST-запроса подряд;
- пользователь жалуется, что нажал кнопку один раз, но получил два письма или два ответа;
- после обновления страницы форма иногда отправляется повторно.
Если у вас включены интеграции через сторонние плагины или собственный код, проверяйте не только Contact Form 7, но и обработчики после успешной отправки. Часто дубль рождается уже там.
Что проверить перед исправлением
Перед правкой кода полезно понять, на каком уровне возникает повтор:
- Откройте DevTools и вкладку Network.
- Отправьте форму один раз и посмотрите, сколько запросов уходит на сервер.
- Проверьте, не срабатывает ли отправка дважды из-за JS-обработчика, который навешан повторно.
- Посмотрите, есть ли у формы интеграция с CRM, webhook или кастомный
functions.php-хук, который отправляет данные еще раз.
Если в Network один запрос, а дубль появляется уже в CRM, значит защита нужна на серверной стороне. Если запросов два, сначала чините фронтенд.
Пошаговое решение: защита от повторной отправки
Надежнее всего сочетать два уровня защиты: блокировку повторного клика на клиенте и серверную проверку на дубликат. Один только JavaScript не спасает от повторного POST-запроса, а одна только серверная проверка не убирает раздражающий двойной клик пользователя.
1. Отключите повторный клик на кнопке отправки
Это не защита от всех дублей, но она убирает самый частый сценарий. Скрипт ниже отключает кнопку после первого нажатия и возвращает ее в исходное состояние, если форма не была успешно отправлена.
document.addEventListener('wpcf7submit', function (event) {
const form = event.target;
const button = form.querySelector('input[type="submit"], button[type="submit"]');
if (button) {
button.disabled = false;
button.value = button.dataset.originalValue || button.value;
}
}, false);
document.addEventListener('click', function (event) {
const button = event.target.closest('.wpcf7 form input[type="submit"], .wpcf7 form button[type="submit"]');
if (!button) return;
if (!button.dataset.originalValue && button.tagName === 'INPUT') {
button.dataset.originalValue = button.value;
}
button.disabled = true;
if (button.tagName === 'INPUT') {
button.value = 'Отправка...';
}
}, false);Этот вариант подходит, если у вас нет сложной кастомной логики. Но он не решает проблему повторного запроса, если пользователь обновил страницу или сеть повторила отправку.
2. Добавьте серверную защиту через nonce и временный токен
В Contact Form 7 уже есть внутренняя защита, но для борьбы с дублями удобнее добавить собственный одноразовый токен и проверять его на сервере. Логика простая: форма получает уникальный токен, а после успешной обработки токен помечается как использованный. Повторный запрос с тем же токеном отклоняется.
Ниже пример для functions.php или небольшого mu-plugin. Он добавляет скрытое поле и проверяет его на этапе валидации.
add_action('wpcf7_init', function () {
wpcf7_add_form_tag('dedupe_token', function () {
$token = wp_generate_uuid4();
return '<input type="hidden" name="dedupe_token" value="' . esc_attr($token) . '">';
});
});
add_filter('wpcf7_validate_hidden', function ($result, $tag) {
if ($tag->name !== 'dedupe_token') {
return $result;
}
$token = isset($_POST['dedupe_token']) ? sanitize_text_field(wp_unslash($_POST['dedupe_token'])) : '';
if (!$token) {
$result->invalidate($tag, 'Не удалось проверить отправку формы.');
return $result;
}
$key = 'cf7_dedupe_' . md5($token);
if (get_transient($key)) {
$result->invalidate($tag, 'Эта форма уже была отправлена.');
return $result;
}
set_transient($key, 1, HOUR_IN_SECONDS);
return $result;
}, 10, 2);Здесь есть важная оговорка: такой код защищает от повторной отправки с тем же токеном, но не от двух разных отправок подряд от одного пользователя. Если вам нужна более строгая антидубль-проверка, добавьте сравнение по email, телефону и времени.
3. Сравнивайте ключевые поля перед созданием заявки
Если форма собирает контактные данные, можно не создавать новую запись, когда в течение короткого промежутка уже приходила заявка с теми же значениями. Это особенно полезно для форм обратного звонка и заявок на консультацию.
add_action('wpcf7_before_send_mail', function ($contact_form) {
$submission = WPCF7_Submission::get_instance();
if (!$submission) {
return;
}
$data = $submission->get_posted_data();
$email = isset($data['your-email']) ? sanitize_email($data['your-email']) : '';
$phone = isset($data['your-phone']) ? preg_replace('/\D+/', '', $data['your-phone']) : '';
if (!$email && !$phone) {
return;
}
$fingerprint = md5(strtolower($email . '|' . $phone));
$key = 'cf7_last_submit_' . $fingerprint;
if (get_transient($key)) {
$submission->set_status('validation_failed');
$submission->set_response('Похоже, эта заявка уже была отправлена.');
return;
}
set_transient($key, time(), 10 * MINUTE_IN_SECONDS);
}, 10, 1);Этот способ лучше использовать аккуратно: если человек реально отправляет форму повторно через несколько минут, вы не должны блокировать его навсегда. Поэтому окно проверки должно быть коротким и понятным для вашей задачи.
Сравнение подходов
| Подход | Что решает | Минус |
|---|---|---|
| Отключение кнопки на фронтенде | Двойной клик, случайное повторное нажатие | Не защищает от повторного запроса |
| Одноразовый токен | Повторную отправку одного и того же запроса | Нужен код и аккуратная интеграция |
| Проверка по email/телефону | Повторы в короткий промежуток | Есть риск ложных срабатываний |
Как проверить, что защита сработала
После внедрения не ограничивайтесь визуальной проверкой формы. Нужна проверка по факту запроса и по итоговому событию.
- Отправьте форму один раз и убедитесь, что в Network уходит один POST.
- Попробуйте нажать кнопку дважды подряд — второй клик не должен создать новую заявку.
- Обновите страницу сразу после отправки и проверьте, не повторяется ли POST.
- Если есть CRM или вебхук, сравните количество входящих событий до и после правки.
Для отладки удобно временно логировать данные в error_log() или смотреть записи в журнале сервера. Если у вас включен плагин логирования, проверяйте именно момент создания заявки, а не только факт показа сообщения об успехе.
Частые ошибки и как их исправить
Защиту ставят только в JavaScript
Это частая ошибка. Пользователь может отправить запрос повторно через DevTools, при нестабильной сети или после обновления страницы. JS нужен, но как дополнительный слой.
Токен живет слишком долго
Если хранить антидубль-токен сутки или неделю, вы начнете блокировать нормальные повторные обращения. Для контактной формы обычно достаточно короткого окна: от нескольких минут до часа, в зависимости от сценария.
Проверка по email ломает реальные повторные заявки
Если человек дважды отправляет форму с одним и тем же email, это не всегда дубль. Например, он мог изменить тему обращения или отправить заявку повторно после ошибки. Поэтому проверку по полям лучше делать только как временную антидубль-защиту, а не как жесткий бизнес-барьер.
Код размещают в теме
Если вы добавляете такую логику в functions.php активной темы, при смене темы защита исчезнет. Для стабильной работы лучше вынести код в небольшой mu-plugin или отдельный плагин проекта.
Безопасность и производительность
Антидубль-логика не должна создавать лишнюю нагрузку. Не пишите каждый запрос в базу без необходимости и не храните бесконечные ключи в transient. Для большинства сайтов transient с коротким TTL достаточно.
Если форма принимает персональные данные, не сохраняйте их в открытом виде в логах. Для сравнения используйте хеш или нормализованный fingerprint, а не полный набор полей. Это уменьшает риск утечки и не мешает проверке дублей.
Если вы уже используете Clearfy Pro для технической чистки сайта, часть лишних дублей и мусорных настроек можно убрать на уровне проекта, но саму защиту от повторной отправки формы все равно лучше реализовать в коде, а не надеяться на общую оптимизацию.
Когда лучше не блокировать повторную отправку жестко
Есть сценарии, где строгая антидубль-защита вреднее, чем полезна. Это формы с длинным циклом принятия решения, заявки на разные услуги с одинаковым email или формы, где пользователь может отправить несколько обращений подряд. В таких случаях лучше:
- показывать предупреждение о повторной отправке;
- ограничивать только очень короткий интервал;
- логировать повторы, но не блокировать их полностью;
- проверять дубли уже на стороне CRM, где виден контекст обращения.
Если задача сводится к тому, чтобы убрать случайные повторные клики и одинаковые заявки из-за сетевых сбоев, связка из отключения кнопки, одноразового токена и короткой серверной проверки закрывает проблему без тяжелых костылей.