В интернет-магазине на WordPress 6.x внедрена интеграция Contact Form 7 с Bitrix24 (20–30 заявок в сутки). Классическая история: менеджеры жалуются, что одни и те же заявки иногда создаются в CRM дважды или даже трижды. Разбирательства приводят к формулировке: “Bitrix24 опять косячит”. На деле виноваты не всегда микросервисы CRM: чаще причины кроются в особенностях интеграционного кода, сетевых соединениях и UX сайта.
Подобные случаи встречаются не только на WordPress: практика показывает, что с дублями встречаются и при интеграции 1С-Битрикс, и на самописных платформах, и даже во встроенных формах Bitrix24. Общее — всегда ручной анализ инцидента и рекламации от CRM-менеджеров, чья статистика и планы продаж начинают страдать.
Я считаю, что в таких историях CRM часто обвиняют слишком рано. Если обработчик формы не хранит собственный след заявки и не умеет спокойно переживать повторный запрос, Bitrix24 просто честно создаёт то, что ему прислали.
Почему заявки дублируются: разбор на примере
- Пользователь заполняет форму и жмёт «Отправить».
- Запрос уходит с сайта на Bitrix24 через API
crm.deal.add. - Из-за медленного ответа (API-спайк: до 8 секунд) пользователь либо жмёт «Отправить» опять, либо форма отправляет повторный запрос автоматически (Contact Form 7 иногда делает это после time-out).
- Два (или больше) запроса доходят до Bitrix24 — создаются дублирующиеся сделки/лиды.
На сервере WordPress возникают локальные задержки, например высокое время ожидания API Bitrix24. Запросы засчитываются как неудачные, хотя на стороне Bitrix24 всё прошло успешно.
Кроме вышеописанного, частой причиной выступает неочевидная фронтенд-логика: неудалённый дубль отправки, ошибочный повтор из-за недоработок AJAX-логики, либо баги в самом Contact Form 7 при нестабильном интернете пользователя. Аналогичные сценарии срабатывают и при обновлении страницы после отправки (классика “F5” и повторное кликание по кнопке, когда форма не блокируется после отправки).
Как определить истинную причину: журнал запросов
Первый шаг — анализировать локальный журнал запросов. На практике помогает MySQL-таблица, где логируются все попытки интеграции по уникальному признаку (например, номеру телефона). Журнал помогает отличить баг CRM от багов интеграционного кода и выяснить:
- Сколько реально пришло запросов на один и тот же лид/сделку;
- Был ли успешно получен ответ от API или сработал повторный запрос;
- С каким набором данных приходят дублирующиеся заявки (различия часто минимальны: пробелы, формат номера телефона и т.д.).
Это позволяет объективно видеть: если одна и та же заявка с одним телефоном и email уходит несколько раз подряд с разницей в несколько секунд, виноват не CRM, а frontend или backend сайта. Если же заявка повторяется с разницей в часы, стоит изучить логи организации — возможно, пользователь обратился повторно. Журнал бывает незаменим при восстановлении после сбоев (например, если интеграция лежала час и “выстрелила” всем массивом после восстановления соединения).
Практические методы борьбы с дублями (опыт внедрения)
Что я обычно проверяю в такой связке:
- Нормализация данных. Приводите номера телефонов к единому формату (см. ниже
normalize_phone). Это критически важно как для определения дубля в API Bitrix24 (crm.duplicate.findbycomm), так и для внутреннего логирования. - Локальная проверка дублей до отправки в CRM. На стороне WordPress храните копии отправок в MySQL с уникальным индексом (по телефону, email или idempotency key).
- Использование idempotency key. Сохраняйте уникальный ключ для каждого события отправки формы (генерируйте по времени, IP и нормализованному телефону). Перед отправкой проверяйте его наличие.
- Реализация ровного retry logic: Если API не ответил, повторяйте не мгновенно, а с задержкой. Так вы снизите острую нагрузку и минимизируете API-спайки.
- Проверка на дубль через Bitrix24 API до
crm.deal.add. Вызовитеcrm.duplicate.findbycommс телефоном/email — если дубль найден, либо обновите, либо заведите новую сущность осознанно. - Используйте inline UX-фишки, чтобы предотвратить повторную отправку формы. Например, блокируйте кнопку после первого клика, показывайте счётчик ожидания (см. материал про UX трения в формах и CRM).
На практике частая ошибка — попытка реализовать де-дубликацию исключительно на стороне Bitrix24. Но если интеграционный код отправил несколько идентичных заявок, дубль уже появился и дальнейшая “склейка” приведёт к потере данных или усложнит автоматизацию. Поэтому локальная логика на фронте и в middleware здесь не украшение, а страховка.
Пример кода: нормализация номера телефона
function normalize_phone($phone) { // Оставляем только цифры; русские номера к +7 $digits = preg_replace('/\D/', '', $phone); if (strlen($digits) === 10) { return "+7" . $digits; } if (strlen($digits) === 11 && ($digits[0] == '7' || $digits[0] == '8')) { return "+7" . substr($digits, 1); } return "+" . $digits; }
Функция помогает избежать ложных дублей из-за расхождений типа “8 (999) 123-45-67” и “+79991234567” — оба приведутся к единому стандарту. На больших объёмах заявок это избавляет от третьей части случайных дублей, возникающих только из-за банального форматирования номера.

Idempotency key — пример реализации
function generate_idempotency_key($phone, $email, $form_id, $time) { $data = $form_id . ":" . normalize_phone($phone) . ":" . strtolower(trim($email)) . ":" . date('YmdHi', $time); return hash('sha256', $data); }
Idempotency key удобно хранить в своей заявочной таблице рядом с исходными данными формы и ID внутренней попытки отправки.
Для надёжности включайте в ключ не только телефон и email, но и идентификатор формы, дату (до минуты) и, опционально, IP или user-agent. Это позволит отсечь даже сложные коллизии и защитит от массовых дублей из-за скриптовых ошибок или неожиданных поведений пользователя. При попытке отправить заявку с тем же key MySQL просто отклонит дубликат по уникальному индексу — серверная защита.
MySQL: хранение заявок с уникальностью по ключу
CREATE TABLE leads_journal ( id INT AUTO_INCREMENT PRIMARY KEY, idempotency_key CHAR(64) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, phone VARCHAR(32), email VARCHAR(128), data JSON, UNIQUE KEY uk_unique_form (idempotency_key) );
Такой подход страхует вас на случай коллизий и аварий интеграционного кода: MySQL не даст вставить дублирующую заявку (INSERT IGNORE/ON DUPLICATE KEY UPDATE).
На практике, если интеграция отправляет тот же набор полей повторно (например, из-за network timeout или разрыва соединения), MySQL таблица не даёт вставить дубликат — такое логирование удобно и для расследования инцидентов, и для масштабирования, когда несколько серверов параллельно отрабатывают очереди заявок.
Автоматизация и порядок восстановления после инцидентов
Создание локального лога заявок и внедрение политики уникальности дают не только защиту от дублей, но и облегчают разбор ошибок при восстановлении после сбоев или переноса на новый сервер. Проверьте чек-лист для подобных операций в подробном гиде по резервным копиям и восстановлению WordPress-Bitrix24.
Любая авария коммуникации (например, выпадение сети или DDoS на сервере) может привести к попыткам массового повторного экспорта заявок в Bitrix24. Без внутреннего журнала восстановить реальные статусы очень сложно — нет истории, откуда и с каким идентификатором ушла заявка, и чем на самом деле завершилась попытка. Локальная история запросов — залог разруливания подобных катастроф.
Ограничения алгоритмов поиска дублей
- Повторные покупки и разные товары. В e-commerce пользователи могут делать осознанные повторные заявки с того же телефона или email — это не дубли, а новые сделки.
- Разные направления или города. В сфере услуг клиент может обращаться за разными услугами с одной и той же контактной информацией.
- Общие телефоны (семья, компания). Несколько человек могут отправлять заявки с одного и того же номера.
- Массовое объединение контактов опасно. Нельзя автоматически склеивать сущности на базе только телефона/e-mail: есть опасность потерять заказы или «проглотить» новые заявки.
Важно помнить: любые автообъединения (merge) в CRM требуют особой настройки бизнес-логики. Лид, связанный с повторным обращением клиента, должен обрабатываться по-разному, в зависимости от направления бизнеса, типа услуги и периода между заявками. В противном случае есть риск “обнулить” важные заказы, по ошибке причислив их к истории уже существующего клиента.
Рекомендации: как реализовать защиту на практике
- Логируйте каждый исходящий запрос в локальной базе с уникальностью по ключу.
- Нормализуйте контактные данные.
- Внедрите задержанный повторный запрос, если API не отвечает.
- Добавьте UX-контроль повторной отправки приложения (блокировка кнопки, информативный loader).
- Используйте проверки дублей на стороне Bitrix24 только как вторую линию защиты.
Реализация этих пунктов занимает от 1 до 3 дней тестирования даже в небольших проектах, но экономит десятки часов на поддержке. Отдельно стоит обратить внимание на тестирование при переходе с http на https, смене серверов или обновлении плагинов интеграции: такие события почти всегда становятся триггером волн “невидимых” дублей.
Вывод
В большинстве случаев виноваты не баги Bitrix24, а гонки во фронтенде/бэкенде сайта и программные реализации интеграции. Правильный журнал, идемпотентные ключи, уникальные индексы в БД и грамотный UX изолируют проблему дублей и позволяют масштабировать интеграцию без риска хаоса в CRM. Тем не менее, попытки автоматической «склейки» данных должны быть осторожными — так вы не потеряете важные новые заказы.
Лучшей практикой всегда остаётся комплексная защита: логирование, проверки на дубль как локально, так и на стороне CRM, нормализация контактов и забота о пользовательском опыте. Такой подход минимизирует человеческий фактор и программные ошибки, а ваша CRM наполняется только действительно уникальными актуальными сделками.