КейсыРастИИшка
Интеграции · устойчивость событий

Повторные вебхуки CRM: одно событие не должно создавать две задачи

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

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

Сотрудник принимает повторяющиеся конверты и направляет одно событие к одной рабочей задаче
Редакционная иллюстрация к теме статьи; не фотография клиентского проекта.

Почему событие вообще приходит повторно

Вебхук — уведомление, которое одна система отправляет другой по HTTP. Отправитель не всегда знает, что произошло на стороне получателя. Запрос мог дойти, действие — выполниться, а ответ — потеряться по дороге. Для отправителя это неуспешная доставка, поэтому он повторяет попытку. Для бизнеса первая попытка уже была вполне успешной. Между этими двумя взглядами и появляется лишнее письмо клиенту.

В документации Stripe отдельно описаны повторные доставки, дубли событий и отсутствие гарантированного порядка. Это поведение конкретного сервиса, а не обещание одинакового режима у всех CRM. При настройке интеграции прочитайте правила своего поставщика: когда он повторяет запрос, сколько хранит события и каким идентификатором обозначает доставку.

Повтор не обязательно вызван аварией. Сотрудник может вручную запросить повторную отправку, очередь — восстановиться после паузы, а система — доставить накопившиеся уведомления. Попытка запретить все повторы обычно заканчивается потерей полезных событий. Правильная цель состоит в том, чтобы допускать повторную доставку и при этом управлять повторением действия.

Сначала разделите три сущности: бизнес-объект, событие об изменении и конкретную попытку доставки. У одной сделки много событий, у одного события несколько попыток. Если считать идентификатор сделки ключом всех повторов, интеграция перестанет обрабатывать её следующие изменения. Если считать каждую попытку новым событием, она будет создавать дубли при каждом сетевом сбое.

Договоритесь, какое действие должно произойти один раз

Фраза «убрать дубли» слишком расплывчата. Запишите правило в терминах бизнеса: при первом переходе сделки в согласованный статус создаётся одна задача на подготовку предложения; повтор того же события не создаёт вторую; новый согласованный переход может создать новую задачу. Отдельно решите, что происходит при возврате на предыдущий этап и повторном прохождении. Это уже логика процесса, а не особенность сети.

Для разных действий нужны разные границы. Уведомление сотруднику, изменение поля, списание средств и создание заказа имеют неодинаковую цену ошибки. Иногда повторная запись того же значения безопасна, а повторное письмо уже нежелательно. Не переносите защиту одного шага на весь сценарий без анализа: факт получения события не доказывает, что каждый последующий шаг завершился.

Что повторилосьПримерНужная реакция
Та же доставкаОтправитель не дождался ответаУзнать событие и не повторять завершённое действие
Новое событие той же сделкиПосле суммы изменился ответственныйОбработать по своему правилу
Повтор бизнес-действияРазные события про одну уже созданную задачуПроверить ключ самой операции
Старое событие после новогоУведомление о прошлом статусе задержалосьНе откатывать актуальное состояние без основания
Два клиента с похожими даннымиСовпало имя, но обращения разныеНе удалять событие по поверхностному сходству

Последняя строка особенно важна: защита от повторных вебхуков не равна объединению клиентов по телефону или имени. Это разные задачи с разными доказательствами. Транспортный повтор узнаётся по согласованной идентичности события или операции. Совпадение текстов двух обращений ещё не делает их одним обращением. Иногда клиент действительно отправляет второй запрос, и бизнес должен его увидеть.

У одной сделки много событий, у события несколько попыток. Ключ доставки и ключ бизнес-операции выбирают по разным основаниям.
У одной сделки много событий, у события несколько попыток. Ключ доставки и ключ бизнес-операции выбирают по разным основаниям.

Из чего складывается устойчивый ключ

Предпочтителен идентификатор события, который выдаёт источник и сохраняет при повторной доставке. Дополните его контекстом: системой-источником и организацией, если интеграция обслуживает несколько компаний. Одинаковые локальные номера у разных клиентов не должны сталкиваться в общей таблице. Разделение по организациям нужно проверять и в ключе, и в запросах, и в правах доступа.

В рекомендациях GitHub по вебхукам описан идентификатор X-GitHub-Delivery; при повторной отправке той же доставки он сохраняется. Это удобный пример, но не универсальное название заголовка. Для вашей CRM найдите её документированный идентификатор и проверьте реальное поведение на тестовом повторе.

Если стабильного идентификатора нет, разработчик проектирует составной ключ из подтверждённых полей. В него могут входить код объекта, тип изменения и версия объекта. Простое хеширование всего JSON иногда ненадёжно: порядок полей или служебная метка времени меняются, хотя смысл события прежний. Обратная ошибка — слишком короткий ключ, который объединяет два законных действия.

Ключ бизнес-операции может отличаться от ключа доставки. Например, несколько уведомлений сообщают, что счёт оплачен, но заказ на комплектацию должен создаваться один для этого счёта и версии процесса. Защита по событию остановит только повтор каждого уведомления. Защита по операции остановит повтор результата, который источники описали разными событиями. Сначала согласуйте это различие с владельцем процесса.

Почему проверки «есть ли уже запись» недостаточно

Представьте два запроса, пришедших одновременно. Первый проверил таблицу — записи нет. Второй проверил — тоже нет. Оба создали задачу, затем оба записали, что событие обработано. Каждая строка программы по отдельности выглядит разумно. Вместе они устроили гонку, а менеджеру снова достались две задачи. Последовательная ручная проверка такого дефекта обычно не обнаруживает.

Нужна защита, которая остаётся верной при конкурирующих запросах. Например, уникальное ограничение в базе на согласованный ключ и атомарная попытка занять обработку. Документация PostgreSQL об ограничениях объясняет механизм уникальности для столбца или набора столбцов. Конкретную схему, обработку конфликтов и транзакции выбирают под используемую базу и сценарий.

Не держите единственный список обработанных событий в памяти приложения. Он исчезнет при перезапуске и может быть разным у нескольких экземпляров сервиса. Память удобна для ускорения, но основание решения о повторе должно переживать штатные сбои. Иначе после обновления кода интеграция дружно забудет, что уже работала, и начнёт знакомиться с клиентами заново.

Одного уникального ограничения тоже недостаточно для всего внешнего мира. Оно защищает запись в своей базе, но не делает транзакцией отправку письма сторонним сервисом. Если письмо ушло, а подтверждение не сохранилось, повтор остаётся неоднозначным. Здесь нужны идентификатор операции у получателя, проверка состояния или отдельная процедура разбора неопределённого исхода.

Состояние обработки важнее галочки «получено»

Храните различимые состояния: принято, ожидает обработки, выполняется, завершено, требуется повтор, нужен разбор. Названия могут быть другими, но смысл должен сохраняться. Событие, принятое в надёжное хранилище, ещё не означает созданную задачу. Событие в работе нельзя навсегда считать завершённым только потому, что один сервер когда-то начал его обрабатывать.

Для зависшей обработки нужен порядок восстановления. Через какое время её можно признать прерванной? Как убедиться, что старый исполнитель не продолжает работать? Какие шаги безопасно повторить? Универсального срока нет: короткая запись в CRM и длительная подготовка документа живут по разным правилам. Важно, чтобы сбой превращался в видимое состояние, а не в вечное «в процессе».

Если сценарий состоит из нескольких действий, фиксируйте их результаты отдельно. Карточка обновлена, задача создана, письмо не отправлено — это полезная картина. Общая отметка «ошибка» заставляет повторять всё целиком. Общая отметка «успех» скрывает недоставленное письмо. Чем дороже действие, тем важнее понимать его собственный исход и возможность безопасного продолжения.

Для внешнего вызова используйте поддерживаемый получателем ключ идемпотентности, если такой механизм есть и подходит операции. Само наличие одноимённого поля не гарантирует нужных свойств: уточните срок хранения ключа, область его уникальности и поведение при другом содержимом. Когда механизма нет, проектируйте сверку результата и ручной разбор неоднозначности вместо обещания абсолютного «ровно один раз».

Надёжный приём не равен завершённому действию. Незавершённый или неопределённый исход должен быть виден и иметь способ восстановления.
Надёжный приём не равен завершённому действию. Незавершённый или неопределённый исход должен быть виден и иметь способ восстановления.

Когда отвечать отправителю

Часто обработчик быстро проверяет подлинность и формат, надёжно сохраняет событие, а длительную работу передаёт очереди. Это уменьшает риск таймаута у отправителя. Но ответ об успешном приёме нельзя выдавать до сохранения, если после него событие может бесследно исчезнуть. Быстрый ответ полезен только вместе с надёжной передачей ответственности.

Время ожидания и правила повторов отличаются у поставщиков. Не зашивайте привычное число секунд из другого проекта. Найдите действующий контракт CRM, ограничения прокси и сроки обработки у вашей системы. Затем убедитесь, что очередь действительно принимает запись до ответа и что ошибка сохранения возвращает ожидаемый отправителем статус. Иначе журнал будет полон зелёных ответов при потерянной работе.

Проверяйте подлинность по механизму источника: подписи, секрету или другому документированному способу. Узнанный идентификатор не является доказательством подлинности. Если принимать любую запись с похожим номером, посторонний запрос способен занять ключ настоящего события. Защита от повторов и проверка источника решают разные задачи и должны работать совместно.

Не записывайте в журналы секреты и полное содержимое клиентских данных без необходимости. Для диагностики обычно достаточно технических идентификаторов, статуса, номера попытки и безопасного описания ошибки. Возможность воспроизвести сбой не требует превращать лог в неуправляемую копию всей CRM. Доступ и срок хранения диагностических сведений согласуйте отдельно.

События пришли не по порядку: что теперь

Сделка сначала перешла в «Согласовано», затем в «Оплачено». Уведомление об оплате пришло быстро, о согласовании — с задержкой. Если обработчик слепо записывает статус из каждого события, он вернёт оплаченную сделку назад. Повторов здесь может вообще не быть: оба события новые и допустимые. Поэтому защита от дублей не заменяет правило обработки устаревшего изменения.

Когда источник даёт надёжную версию объекта, можно сравнивать её с применённой версией. Если такой версии нет, иногда правильнее получить актуальное состояние из API и использовать событие как повод для проверки. Но это тоже проектное решение: чтение текущего состояния может пропустить важный исторический переход, если именно он запускает отдельное действие. Различайте синхронизацию карточки и реакцию на факт перехода.

Метки времени полезны, но требуют осторожности: разные часы, одинаковое время нескольких изменений и задержки записи ограничивают их точность. Не считайте любую поздно доставленную запись устаревшей только по времени получения. Сначала разберите, какое время передаёт источник: создания события, изменения объекта или попытки доставки. Эти значения отвечают на разные вопросы.

Для переходов назад договоритесь с владельцем продаж. Возврат сделки может быть законным исправлением, а не сетевой ошибкой. Система должна отличать новый разрешённый возврат от старого уведомления, которое наконец добралось до сервера. Здесь нужна предметная логика процесса и доступные версии, а не универсальное правило «статус всегда движется только вперёд».

Сколько помнить событие и как повторять вручную

Срок хранения ключей выбирают с учётом окна повторов источника, ручных переотправок и требований к расследованию. Если удалить запись раньше возможного повтора, старое событие снова станет новым. Если хранить всё бессрочно без назначения, обслуживание и доступ к данным усложняются. Зафиксируйте срок, основание и поведение после его окончания, особенно для действий с высокой ценой повторения.

Ручная кнопка «повторить» должна объяснять, что именно повторяется. Повторить доставку уведомления, продолжить незавершённый шаг и заново выполнить бизнес-действие — разные полномочия. Для третьего может требоваться явное согласование и новый идентификатор операции. Иначе оператор, пытаясь исправить письмо, повторно создаст заказ. Интерфейс восстановления тоже является частью защиты от дублей.

Сохраняйте связь между исходным событием, повторными попытками и результатом. Тогда специалист видит, что пять доставок привели к одной задаче, а не к пяти независимым успехам. Номер попытки полезен для диагностики, но не должен автоматически менять ключ бизнес-операции. Повторная попытка должна искать уже существующий результат прежде, чем создавать новый.

Шесть испытаний, которые стоит провести на стенде

ИспытаниеКакой риск проверяемОжидаемое наблюдение
Два последовательных повтораОбычная повторная доставкаОдна задача, узнаваемый повтор
Два одновременных запросаГонка проверки и записиОдин исполнитель операции
Перезапуск после приёмаПотеря состояния в памятиСобытие остаётся доступным для обработки
Сбой после внешнего действияНеизвестный исход ответаСверка результата или видимый разбор
Старое событие после новогоОткат актуального состоянияПрименяется согласованное правило версий
Одинаковый номер в двух организацияхСмешение клиентских контуровНезависимые разрешённые операции

Запускайте эти проверки на изолированных данных и тестовых получателях. Не проверяйте три повторных письма на реальном клиенте ради уверенности, что четвёртого не будет. Для подготовки окружения полезен материал о тестовом стенде интеграции CRM. Важно проверить и интерфейс, и факт появления результата у получателя, а не только ответ вашего обработчика.

Приёмочный протокол должен содержать идентификаторы тестовых событий, ожидаемое число действий и фактическое число результатов. «Все запросы вернули 200» не отвечает на вопрос, создалась ли одна задача. Дополнительно проверьте, что законное новое событие после повтора не заблокировано. Защита, которая останавливает всю дальнейшую работу сделки, формально избавляет от дублей, но бизнесу от этого не легче.

При испытании сверяют число реальных действий и законную дальнейшую обработку. Несколько успешных HTTP-ответов не доказывают одну созданную задачу.
При испытании сверяют число реальных действий и законную дальнейшую обработку. Несколько успешных HTTP-ответов не доказывают одну созданную задачу.

Что контролировать после запуска

Полезны количество принятых событий, узнаваемых повторов, завершённых операций, зависших обработок и случаев с неопределённым исходом. Сам рост повторных доставок не всегда авария: источник мог восстановиться после перерыва. Оцените, справилась ли защита и увеличилась ли задержка получения результата. Сигнал должен вести к конкретной проверке, а не просто сообщать, что в интеграции опять происходят события.

Сравнивайте принятые события с бизнес-результатами по согласованным правилам. Не у каждого события обязана появиться задача: часть изменений может быть информационной. Но у каждого события должен быть объяснимый исход — выполнено, безопасно пропущено, ожидает или требует разбора. Такая сверка помогает находить и дубли, и тихие потери, которые обычная статистика HTTP-ответов не показывает.

Если планируете разработку интеграции, включите в задачу правила идентичности события, границы операции, порядок восстановления и примеры повторов. Это небольшая часть постановки по сравнению с общей автоматизацией, но именно она определяет поведение при обычных сетевых сбоях. Устойчивость проявляется не в том, что повтор никогда не пришёл, а в том, что бизнес заметил его только в техническом журнале.

Цифра — ai-crm и работа ии-агентов со сделками; состав работы и ограничения указаны на странице направления.

Частые вопросы

Почему один вебхук приходит несколько раз?

Отправитель может не получить подтверждение, хотя получатель уже выполнил действие. Повтор также возникает при восстановлении или ручной переотправке. Режим зависит от сервиса. Интеграция должна узнавать событие и безопасно продолжать обработку, не считая каждую попытку новым бизнес-фактом.

Какой ключ использовать для защиты от повторов?

Предпочтителен стабильный идентификатор события источника с нужным контекстом системы и организации. Иногда отдельно нужен ключ бизнес-операции. Номер сделки нельзя автоматически использовать вместо всех событий этой сделки: он может заблокировать законные последующие изменения.

Достаточно ли проверить наличие события перед записью?

Нет: параллельные запросы могут одновременно увидеть отсутствие записи и выполнить действие дважды. Нужна согласованная защита от гонки, например уникальность и атомарное занятие обработки в подходящем хранилище. Внешние операции требуют отдельного контроля результата и восстановления.

Можно ли гарантировать ровно одно внешнее действие?

Не обещайте это без проверки возможностей всех участников. После внешнего действия ответ может потеряться, и исход станет неопределённым. Используйте поддерживаемые ключи идемпотентности, сверку и видимый разбор. Уникальная запись в своей базе не делает внешний вызов частью той же транзакции.

Как проверять защиту от повторных вебхуков?

Проверьте последовательные и одновременные повторы, перезапуск, сбой после внешнего действия и обратный порядок событий. Сверяйте число реальных результатов, а не только HTTP-ответы. Используйте изолированные данные и контролируемых получателей, чтобы испытание не создало настоящие дубли клиентам.

Проверить ключевую операцию до разработки

Опишите одну операцию пользователя, системы-участники и подтверждение успешного результата. На разборе определим прототип и критерии приёмки, включая ошибки и повторы.

Посмотреть подход к разработке ↗

Разобрать интеграцию ↗

Сравниваете несколько направлений? Соберите бриф по задаче.