Сценарий простой: форма уходит, письмо приходит, но в CRM, почте или таблице заявок появляется второй дубль с тем же email. Это не то же самое, что двойная отправка формы в браузере — здесь проблема часто в логике обработки данных после отправки. В Contact Form 7 такое обычно всплывает, когда одну и ту же форму используют для нескольких получателей, подключают сторонние интеграции или пытаются проверять email уже после сабмита.
Ниже разберём, где именно возникает дубль, как его отловить и что делать без выдуманных хуков и «магии» в functions.php.
Когда это действительно дубль, а не два разных события
Сначала важно понять, что именно вы видите. Если письмо пришло один раз, а в CRM запись появилась дважды, это чаще всего не проблема Contact Form 7 как такового. Форма отправилась один раз, но:
- интеграция с вебхуком или API сработала повторно;
- скрипт на стороне темы или плагина дважды подписался на событие;
- почтовый сервис создал две записи из-за повторной обработки одного и того же webhook-пакета;
- на странице есть две одинаковые формы с одинаковым action, и пользователь отправляет не ту, что вы ожидаете.
Если же в админке Contact Form 7 вы видите два отдельных сообщения, тогда уже нужно смотреть на отправку формы, JavaScript и серверную обработку. Но в этой статье речь именно о запрете повторной фиксации заявки на один и тот же email.
Диагностика: где появляется повтор
Проверка должна идти по цепочке. Не пытайтесь сразу писать фильтры, пока не увидите, на каком шаге запись дублируется.
1. Проверьте, что форма отправляется один раз
Откройте DevTools в браузере, вкладку Network, отправьте форму и посмотрите, сколько запросов уходит на endpoint Contact Form 7. Для обычной отправки должен быть один POST-запрос. Если их два, ищите:
- двойной обработчик клика по кнопке отправки;
- дублирующийся JavaScript в теме;
- скрипт, который повторно вызывает
submit()или имитирует клик; - кешированный/вставленный дважды блок формы.
2. Сверьте email в исходных данных
Если дубль появляется только в CRM, посмотрите, одинаковый ли email у обеих записей. Иногда форма отправляет один и тот же шаблон, но поле email подставляется из автозаполнения браузера или из скрытого поля. Тогда проблема не в дубле, а в том, что вы принимаете одинаковые данные как две разные заявки.
3. Проверьте интеграции
Если подключены Zapier, Make, webhook в сторонний сервис или собственный endpoint, проверьте логи на стороне получателя. Очень часто один и тот же payload приходит повторно после таймаута или из-за повторной попытки доставки. Это уже вопрос идемпотентности: один email должен создавать одну запись, а повторный запрос — обновлять существующую или игнорироваться.
Рабочая схема: отсекаем повтор по email до создания записи
Самый надёжный вариант — проверять email на стороне WordPress до того, как вы отправите данные дальше в CRM или сохраните их в своей таблице. Для этого удобно использовать хук Contact Form 7 wpcf7_before_send_mail. Он срабатывает перед отправкой письма и позволяет остановить обработку формы.
Ниже пример: если email уже встречался в записях формы, отправку можно прервать. В реальном проекте лучше хранить не просто email, а связку email + form_id, чтобы одна и та же почта могла оставить заявку в разных формах.
<?php
add_action( 'wpcf7_before_send_mail', 'cf7_block_duplicate_email' );
function cf7_block_duplicate_email( $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'] ) : '';
if ( ! $email || ! is_email( $email ) ) {
return;
}
$form_id = (int) $contact_form->id();
$key = 'cf7_email_' . $form_id . '_' . md5( strtolower( $email ) );
if ( get_transient( $key ) ) {
$contact_form->skip_mail = true;
$submission->set_status( 'validation_failed' );
$submission->set_response( 'Заявка с этим email уже была отправлена.' );
return;
}
set_transient( $key, 1, DAY_IN_SECONDS );
}Что здесь важно:
your-emailдолжен совпадать с именем поля в форме;- мы нормализуем email через
sanitize_email()иis_email(); - ключ transient привязан к ID формы;
- повтор в течение суток блокируется без записи в почту.
Если вам нужно не блокировать заявку полностью, а только не создавать дубликат в CRM, то вместо skip_mail лучше оставить письмо, но в интеграции проверять, существует ли уже такой email.
Если дубли создаёт CRM или вебхук
Когда Contact Form 7 отправляет данные один раз, а повтор появляется уже после интеграции, логика должна быть идемпотентной. Это значит, что повторный запрос с тем же email не должен создавать новую сущность.
Практически это решается так:
- на стороне WordPress отправляйте в CRM не только email, но и уникальный ключ заявки;
- на стороне CRM ищите запись по email перед созданием новой;
- если запись уже есть, обновляйте её или пропускайте создание;
- логируйте ответ сервиса, чтобы видеть, где именно возник повтор.
Если вы пишете свой обработчик webhook, используйте email как один из ключей, но не единственный. Лучше хранить form_id, email и дату первой отправки. Тогда можно отдельно решать, считать ли повтор через час ошибкой, а через неделю — новой заявкой.
Пример проверки перед отправкой в внешний API
<?php
function cf7_send_to_crm_once( $email, $payload ) {
$email = sanitize_email( $email );
if ( ! is_email( $email ) ) {
return new WP_Error( 'invalid_email', 'Некорректный email' );
}
$hash = md5( strtolower( $email ) . '|' . ( $payload['form_id'] ?? 0 ) );
if ( get_option( 'cf7_crm_sent_' . $hash ) ) {
return true;
}
$response = wp_remote_post( 'https://crm.example.com/api/leads', array(
'timeout' => 15,
'headers' => array(
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $payload ),
) );
if ( is_wp_error( $response ) ) {
return $response;
}
$code = wp_remote_retrieve_response_code( $response );
if ( $code >= 200 && $code < 300 ) {
update_option( 'cf7_crm_sent_' . $hash, time(), false );
}
return $response;
}Это не идеальная база данных для больших проектов, но для небольшой установки WordPress такой подход уже убирает повторные записи. Если заявок много, лучше хранить историю в отдельной таблице, а не в options.
Чек-лист перед внедрением
- Проверьте имя поля email в форме Contact Form 7.
- Убедитесь, что дубль появляется именно после отправки, а не из-за автозаполнения.
- Посмотрите Network: один ли POST уходит при сабмите.
- Проверьте, не дублируется ли JS-код темы или плагина.
- Если есть CRM/webhook, проверьте логи повторных запросов.
- Решите, где блокировать дубль: в WordPress или на стороне внешнего сервиса.
Проверка результата после внедрения
После добавления защиты не ограничивайтесь одной тестовой отправкой. Сделайте три проверки:
- Отправьте форму с новым email — заявка должна пройти.
- Отправьте ту же форму с тем же email ещё раз — повтор должен быть заблокирован или не должен создаваться в CRM.
- Проверьте журнал внешнего сервиса: второй запрос либо не приходит, либо приходит, но не создаёт новую запись.
Если вы используете transient-логику, учитывайте TTL. Через сутки или другой заданный срок email снова сможет пройти. Это нормально, если вы сознательно выбрали временную защиту от дублей, а не вечную блокировку.
Частые ошибки и как их исправить
Проверяют только письмо, а не запись в CRM
Письмо может уйти один раз, а CRM создаст две карточки. В таком случае проблема не в почте и не в Contact Form 7, а в интеграции. Ищите повторный webhook или отсутствие проверки существующей записи.
Ставят блокировку по email без учёта формы
Если один и тот же email может отправлять заявки из разных форм, глобальная блокировка сломает рабочий сценарий. Привязывайте ключ к form_id.
Используют wp_options для большого потока заявок
Для пары десятков лидов это терпимо, но при росте объёма options разрастается и усложняет обслуживание. Если заявок много, лучше отдельная таблица или внешний сервис с нормальной дедупликацией.
Не нормализуют email
Test@Example.com и test@example.com должны считаться одним и тем же адресом. Перед сравнением приводите email к нижнему регистру и очищайте через sanitize_email().
Пытаются лечить дубль через кеш
Кеш страницы не решает проблему повторной фиксации заявки. Более того, агрессивный кеш может скрыть реальную причину, если форма и скрипты загружаются не так, как ожидается.
Что выбрать: плагин, код или проверка на стороне CRM
| Подход | Когда подходит | Минус |
|---|---|---|
| Код в WordPress | Нужно быстро отсеять повтор по email до отправки дальше | Нужно поддерживать свой код |
| Проверка в CRM | Данные уже уходят во внешний сервис | Дубль всё равно доходит до интеграции |
| Плагин-автоматизация | Есть сложные сценарии и несколько форм | Дополнительная зависимость и нагрузка |
Если у вас уже есть плагин для технической чистки WordPress, например Clearfy Pro, он может помочь убрать лишние дубли и мусорные настройки в самой установке, но саму логику дедупликации заявок всё равно лучше держать в коде или в CRM. Для формы это вопрос бизнес-логики, а не только SEO или оптимизации.
Безопасность и производительность
Не храните email в открытом виде там, где это не требуется. Если вы делаете ключ дедупликации, достаточно хеша. Это снижает риск утечки персональных данных из логов и опций. Также не забывайте ограничивать срок жизни transient, иначе вы получите вечную блокировку повторных заявок.
Если форма критична для бизнеса, добавьте логирование: кто отправил, когда, какой form_id, какой ответ вернул внешний сервис. Это поможет быстро отличить реальный дубль от повторной доставки webhook.
И ещё один практический момент: если вы тестируете форму на staging, не копируйте боевую базу без очистки transient и логов. Иначе можно получить ложные срабатывания и сделать вывод, что защита сломана, хотя она просто видит старый ключ.