Представим новую заявку. Клиент оставил одну заявку. CRM создала одну сделку. Интеграция поставила менеджеру три одинаковые задачи и трижды отправила уведомление. Разработчик смотрит в журнал: «Но ведь событие действительно пришло три раза». Менеджер смотрит в календарь и уже начинает подозревать, что клиент хочет купить компанию целиком.
Повторный вебхук должен быть безопасен для бизнеса: повторная доставка одного события не должна повторять уже выполненное действие. Для этого нужны устойчивый ключ события, сохранённое состояние обработки и защита на уровне создаваемой операции. Один фильтр в памяти сервера не закрывает сбои, параллельные запросы и перезапуски. Разберём, как проверить интеграцию CRM до появления дублей задач, писем и заказов.

Почему событие вообще приходит повторно
Вебхук — уведомление, которое одна система отправляет другой по HTTP. Отправитель не всегда знает, что произошло на стороне получателя. Запрос мог дойти, действие — выполниться, а ответ — потеряться по дороге. Для отправителя это неуспешная доставка, поэтому он повторяет попытку. Для бизнеса первая попытка уже была вполне успешной. Между этими двумя взглядами и появляется лишнее письмо клиенту.
В документации Stripe отдельно описаны повторные доставки, дубли событий и отсутствие гарантированного порядка. Это поведение конкретного сервиса, а не обещание одинакового режима у всех CRM. При настройке интеграции прочитайте правила своего поставщика: когда он повторяет запрос, сколько хранит события и каким идентификатором обозначает доставку.
Повтор не обязательно вызван аварией. Сотрудник может вручную запросить повторную отправку, очередь — восстановиться после паузы, а система — доставить накопившиеся уведомления. Попытка запретить все повторы обычно заканчивается потерей полезных событий. Правильная цель состоит в том, чтобы допускать повторную доставку и при этом управлять повторением действия.
Сначала разделите три сущности: бизнес-объект, событие об изменении и конкретную попытку доставки. У одной сделки много событий, у одного события несколько попыток. Если считать идентификатор сделки ключом всех повторов, интеграция перестанет обрабатывать её следующие изменения. Если считать каждую попытку новым событием, она будет создавать дубли при каждом сетевом сбое.
Договоритесь, какое действие должно произойти один раз
Фраза «убрать дубли» слишком расплывчата. Запишите правило в терминах бизнеса: при первом переходе сделки в согласованный статус создаётся одна задача на подготовку предложения; повтор того же события не создаёт вторую; новый согласованный переход может создать новую задачу. Отдельно решите, что происходит при возврате на предыдущий этап и повторном прохождении. Это уже логика процесса, а не особенность сети.
Для разных действий нужны разные границы. Уведомление сотруднику, изменение поля, списание средств и создание заказа имеют неодинаковую цену ошибки. Иногда повторная запись того же значения безопасна, а повторное письмо уже нежелательно. Не переносите защиту одного шага на весь сценарий без анализа: факт получения события не доказывает, что каждый последующий шаг завершился.
| Что повторилось | Пример | Нужная реакция |
|---|---|---|
| Та же доставка | Отправитель не дождался ответа | Узнать событие и не повторять завершённое действие |
| Новое событие той же сделки | После суммы изменился ответственный | Обработать по своему правилу |
| Повтор бизнес-действия | Разные события про одну уже созданную задачу | Проверить ключ самой операции |
| Старое событие после нового | Уведомление о прошлом статусе задержалось | Не откатывать актуальное состояние без основания |
| Два клиента с похожими данными | Совпало имя, но обращения разные | Не удалять событие по поверхностному сходству |
Последняя строка особенно важна: защита от повторных вебхуков не равна объединению клиентов по телефону или имени. Это разные задачи с разными доказательствами. Транспортный повтор узнаётся по согласованной идентичности события или операции. Совпадение текстов двух обращений ещё не делает их одним обращением. Иногда клиент действительно отправляет второй запрос, и бизнес должен его увидеть.
Из чего складывается устойчивый ключ
Предпочтителен идентификатор события, который выдаёт источник и сохраняет при повторной доставке. Дополните его контекстом: системой-источником и организацией, если интеграция обслуживает несколько компаний. Одинаковые локальные номера у разных клиентов не должны сталкиваться в общей таблице. Разделение по организациям нужно проверять и в ключе, и в запросах, и в правах доступа.
В рекомендациях GitHub по вебхукам описан идентификатор X-GitHub-Delivery; при повторной отправке той же доставки он сохраняется. Это удобный пример, но не универсальное название заголовка. Для вашей CRM найдите её документированный идентификатор и проверьте реальное поведение на тестовом повторе.
Если стабильного идентификатора нет, разработчик проектирует составной ключ из подтверждённых полей. В него могут входить код объекта, тип изменения и версия объекта. Простое хеширование всего JSON иногда ненадёжно: порядок полей или служебная метка времени меняются, хотя смысл события прежний. Обратная ошибка — слишком короткий ключ, который объединяет два законных действия.
Ключ бизнес-операции может отличаться от ключа доставки. Например, несколько уведомлений сообщают, что счёт оплачен, но заказ на комплектацию должен создаваться один для этого счёта и версии процесса. Защита по событию остановит только повтор каждого уведомления. Защита по операции остановит повтор результата, который источники описали разными событиями. Сначала согласуйте это различие с владельцем процесса.
Почему проверки «есть ли уже запись» недостаточно
Представьте два запроса, пришедших одновременно. Первый проверил таблицу — записи нет. Второй проверил — тоже нет. Оба создали задачу, затем оба записали, что событие обработано. Каждая строка программы по отдельности выглядит разумно. Вместе они устроили гонку, а менеджеру снова достались две задачи. Последовательная ручная проверка такого дефекта обычно не обнаруживает.
Нужна защита, которая остаётся верной при конкурирующих запросах. Например, уникальное ограничение в базе на согласованный ключ и атомарная попытка занять обработку. Документация PostgreSQL об ограничениях объясняет механизм уникальности для столбца или набора столбцов. Конкретную схему, обработку конфликтов и транзакции выбирают под используемую базу и сценарий.
Не держите единственный список обработанных событий в памяти приложения. Он исчезнет при перезапуске и может быть разным у нескольких экземпляров сервиса. Память удобна для ускорения, но основание решения о повторе должно переживать штатные сбои. Иначе после обновления кода интеграция дружно забудет, что уже работала, и начнёт знакомиться с клиентами заново.
Одного уникального ограничения тоже недостаточно для всего внешнего мира. Оно защищает запись в своей базе, но не делает транзакцией отправку письма сторонним сервисом. Если письмо ушло, а подтверждение не сохранилось, повтор остаётся неоднозначным. Здесь нужны идентификатор операции у получателя, проверка состояния или отдельная процедура разбора неопределённого исхода.
Состояние обработки важнее галочки «получено»
Храните различимые состояния: принято, ожидает обработки, выполняется, завершено, требуется повтор, нужен разбор. Названия могут быть другими, но смысл должен сохраняться. Событие, принятое в надёжное хранилище, ещё не означает созданную задачу. Событие в работе нельзя навсегда считать завершённым только потому, что один сервер когда-то начал его обрабатывать.
Для зависшей обработки нужен порядок восстановления. Через какое время её можно признать прерванной? Как убедиться, что старый исполнитель не продолжает работать? Какие шаги безопасно повторить? Универсального срока нет: короткая запись в CRM и длительная подготовка документа живут по разным правилам. Важно, чтобы сбой превращался в видимое состояние, а не в вечное «в процессе».
Если сценарий состоит из нескольких действий, фиксируйте их результаты отдельно. Карточка обновлена, задача создана, письмо не отправлено — это полезная картина. Общая отметка «ошибка» заставляет повторять всё целиком. Общая отметка «успех» скрывает недоставленное письмо. Чем дороже действие, тем важнее понимать его собственный исход и возможность безопасного продолжения.
Для внешнего вызова используйте поддерживаемый получателем ключ идемпотентности, если такой механизм есть и подходит операции. Само наличие одноимённого поля не гарантирует нужных свойств: уточните срок хранения ключа, область его уникальности и поведение при другом содержимом. Когда механизма нет, проектируйте сверку результата и ручной разбор неоднозначности вместо обещания абсолютного «ровно один раз».
Когда отвечать отправителю
Часто обработчик быстро проверяет подлинность и формат, надёжно сохраняет событие, а длительную работу передаёт очереди. Это уменьшает риск таймаута у отправителя. Но ответ об успешном приёме нельзя выдавать до сохранения, если после него событие может бесследно исчезнуть. Быстрый ответ полезен только вместе с надёжной передачей ответственности.
Время ожидания и правила повторов отличаются у поставщиков. Не зашивайте привычное число секунд из другого проекта. Найдите действующий контракт CRM, ограничения прокси и сроки обработки у вашей системы. Затем убедитесь, что очередь действительно принимает запись до ответа и что ошибка сохранения возвращает ожидаемый отправителем статус. Иначе журнал будет полон зелёных ответов при потерянной работе.
Проверяйте подлинность по механизму источника: подписи, секрету или другому документированному способу. Узнанный идентификатор не является доказательством подлинности. Если принимать любую запись с похожим номером, посторонний запрос способен занять ключ настоящего события. Защита от повторов и проверка источника решают разные задачи и должны работать совместно.
Не записывайте в журналы секреты и полное содержимое клиентских данных без необходимости. Для диагностики обычно достаточно технических идентификаторов, статуса, номера попытки и безопасного описания ошибки. Возможность воспроизвести сбой не требует превращать лог в неуправляемую копию всей CRM. Доступ и срок хранения диагностических сведений согласуйте отдельно.
События пришли не по порядку: что теперь
Сделка сначала перешла в «Согласовано», затем в «Оплачено». Уведомление об оплате пришло быстро, о согласовании — с задержкой. Если обработчик слепо записывает статус из каждого события, он вернёт оплаченную сделку назад. Повторов здесь может вообще не быть: оба события новые и допустимые. Поэтому защита от дублей не заменяет правило обработки устаревшего изменения.
Когда источник даёт надёжную версию объекта, можно сравнивать её с применённой версией. Если такой версии нет, иногда правильнее получить актуальное состояние из API и использовать событие как повод для проверки. Но это тоже проектное решение: чтение текущего состояния может пропустить важный исторический переход, если именно он запускает отдельное действие. Различайте синхронизацию карточки и реакцию на факт перехода.
Метки времени полезны, но требуют осторожности: разные часы, одинаковое время нескольких изменений и задержки записи ограничивают их точность. Не считайте любую поздно доставленную запись устаревшей только по времени получения. Сначала разберите, какое время передаёт источник: создания события, изменения объекта или попытки доставки. Эти значения отвечают на разные вопросы.
Для переходов назад договоритесь с владельцем продаж. Возврат сделки может быть законным исправлением, а не сетевой ошибкой. Система должна отличать новый разрешённый возврат от старого уведомления, которое наконец добралось до сервера. Здесь нужна предметная логика процесса и доступные версии, а не универсальное правило «статус всегда движется только вперёд».
Сколько помнить событие и как повторять вручную
Срок хранения ключей выбирают с учётом окна повторов источника, ручных переотправок и требований к расследованию. Если удалить запись раньше возможного повтора, старое событие снова станет новым. Если хранить всё бессрочно без назначения, обслуживание и доступ к данным усложняются. Зафиксируйте срок, основание и поведение после его окончания, особенно для действий с высокой ценой повторения.
Ручная кнопка «повторить» должна объяснять, что именно повторяется. Повторить доставку уведомления, продолжить незавершённый шаг и заново выполнить бизнес-действие — разные полномочия. Для третьего может требоваться явное согласование и новый идентификатор операции. Иначе оператор, пытаясь исправить письмо, повторно создаст заказ. Интерфейс восстановления тоже является частью защиты от дублей.
Сохраняйте связь между исходным событием, повторными попытками и результатом. Тогда специалист видит, что пять доставок привели к одной задаче, а не к пяти независимым успехам. Номер попытки полезен для диагностики, но не должен автоматически менять ключ бизнес-операции. Повторная попытка должна искать уже существующий результат прежде, чем создавать новый.
Шесть испытаний, которые стоит провести на стенде
| Испытание | Какой риск проверяем | Ожидаемое наблюдение |
|---|---|---|
| Два последовательных повтора | Обычная повторная доставка | Одна задача, узнаваемый повтор |
| Два одновременных запроса | Гонка проверки и записи | Один исполнитель операции |
| Перезапуск после приёма | Потеря состояния в памяти | Событие остаётся доступным для обработки |
| Сбой после внешнего действия | Неизвестный исход ответа | Сверка результата или видимый разбор |
| Старое событие после нового | Откат актуального состояния | Применяется согласованное правило версий |
| Одинаковый номер в двух организациях | Смешение клиентских контуров | Независимые разрешённые операции |
Запускайте эти проверки на изолированных данных и тестовых получателях. Не проверяйте три повторных письма на реальном клиенте ради уверенности, что четвёртого не будет. Для подготовки окружения полезен материал о тестовом стенде интеграции CRM. Важно проверить и интерфейс, и факт появления результата у получателя, а не только ответ вашего обработчика.
Приёмочный протокол должен содержать идентификаторы тестовых событий, ожидаемое число действий и фактическое число результатов. «Все запросы вернули 200» не отвечает на вопрос, создалась ли одна задача. Дополнительно проверьте, что законное новое событие после повтора не заблокировано. Защита, которая останавливает всю дальнейшую работу сделки, формально избавляет от дублей, но бизнесу от этого не легче.
Что контролировать после запуска
Полезны количество принятых событий, узнаваемых повторов, завершённых операций, зависших обработок и случаев с неопределённым исходом. Сам рост повторных доставок не всегда авария: источник мог восстановиться после перерыва. Оцените, справилась ли защита и увеличилась ли задержка получения результата. Сигнал должен вести к конкретной проверке, а не просто сообщать, что в интеграции опять происходят события.
Сравнивайте принятые события с бизнес-результатами по согласованным правилам. Не у каждого события обязана появиться задача: часть изменений может быть информационной. Но у каждого события должен быть объяснимый исход — выполнено, безопасно пропущено, ожидает или требует разбора. Такая сверка помогает находить и дубли, и тихие потери, которые обычная статистика HTTP-ответов не показывает.
Если планируете разработку интеграции, включите в задачу правила идентичности события, границы операции, порядок восстановления и примеры повторов. Это небольшая часть постановки по сравнению с общей автоматизацией, но именно она определяет поведение при обычных сетевых сбоях. Устойчивость проявляется не в том, что повтор никогда не пришёл, а в том, что бизнес заметил его только в техническом журнале.
Цифра — ai-crm и работа ии-агентов со сделками; состав работы и ограничения указаны на странице направления.