Интеграции и приложения · 5 мин

Webhook: как обрабатывать повторные и запоздавшие уведомления

Приёмный журнал, версии объектов и восстановление пропусков: как построить обработку webhook, которая выдерживает дубли и нарушение порядка.

Тематическая иллюстрация к материалу: Webhook: как обрабатывать повторные и запоздавшие уведомления

Редакция Romanov Lab · Опубликовано

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

Сначала прочитайте гарантии отправителя

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

Уточните также смысл сообщения. Оно может содержать полный снимок объекта, только изменённые поля или сигнал «перечитайте ресурс». Обработчик, ожидающий полную карточку, способен стереть данные, если получит частичное изменение. Версию формата фиксируют отдельно от версии самого бизнес-объекта.

Не путайте событие, попытку доставки и объект

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

Для учебного контракта определим три поля: source, event_id и entity_id. Дедупликация сообщения действует по паре «источник + событие». Это не обязательные поля всех сервисов. Если отправитель не предоставляет устойчивого идентификатора, способ распознавания повторов требует отдельного соглашения; случайный идентификатор на стороне получателя эту задачу не решит.

Принимайте в журнал до сложной обработки

После проверки источника и допустимого размера сообщения приёмник сохраняет его минимально необходимое содержимое. Уникальность ключа должна обеспечиваться атомарно. Последовательность «проверить отсутствие, затем вставить» без защиты не выдерживает двух одновременных запросов.

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

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

Учебный пример: версия 12 пришла раньше версии 11

Пусть система-источник гарантирует возрастающую ревизию каждого заказа. Локально сохранена ревизия 10. Сначала приходит снимок ревизии 12 со статусом «отправлен», затем снимок 11 со статусом «собран». Обработчик применяет 12 и отмечает 11 как устаревшее событие. Обычная перезапись по времени получения вернула бы заказ назад.

Текущее состояние: заказ Q-18, ревизия 10
Событие E-12: полный снимок, ревизия 12 → применить
Событие E-11: полный снимок, ревизия 11 → не откатывать
Повтор E-12: тот же идентификатор события → не применять повторно

Теперь изменим условие: событие 12 содержит команду «добавить одну позицию», а событие 11 содержит предыдущую операцию, без которой расчёт неверен. Просто отбросить 11 нельзя. Для цепочки изменений может понадобиться дождаться пропуска, запросить историю или получить новый полный снимок. Выбор определяется смыслом данных, а не удобством реализации.

Если версии нет, не изобретайте её из времени

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

Даже при перечитывании существует гонка: два обработчика получили разные версии и завершились в обратном порядке. Запись должна проверять ожидаемую ревизию или выполняться последовательно в пределах одного объекта. Например, If-Match позволяет условное изменение ресурса, если принимающее API действительно поддерживает этот механизм. Добавление заголовка к неподдерживающему его сервису не создаёт защиту.

Разделите применение состояния и побочные действия

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

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

Проверьте восстановление, а не только приём

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

Главный показатель для эксплуатации: можно ли объяснить судьбу каждого принятого события и восстановить объект после пропуска. Число ответов HTTP 200 само по себе этого не показывает.

Нужен план для вашего проекта? Напишите нам в Telegram или по электронной почте. Обсудим разработку, продвижение или поддержку сайта.

Читайте также