Вебхуки приходят не по порядку: почему статус заказа откатывается назад

Заказ уже оплачен, но через несколько секунд снова становится «ожидает оплаты». В журнале нет ошибки: обработчик успешно принял два уведомления. Просто сообщение о более раннем состоянии добралось позже. Повтор запроса и нарушение порядка — разные проблемы, хотя часто появляются вместе.
Две шкалы времени
У события есть момент возникновения у источника и момент получения вашим приложением. На второй влияют очередь провайдера, сетевые повторы, задержки прокси и параллельная работа обработчиков. Сортировать бизнес-состояние только по времени получения нельзя. Даже отметка времени источника не всегда годится для строгого порядка: часы разных узлов могут расходиться, а несколько событий — иметь одинаковую точность.
Сначала прочитайте контракт конкретного провайдера. Он может передавать последовательную версию объекта, уникальный номер события, время изменения или только сигнал «данные обновились». Эти варианты требуют разной логики. Придуманное поле version, которого нет в контракте, не сделает интеграцию надёжнее.
| Доступная информация | Подход | Ограничение |
|---|---|---|
| Монотонная версия объекта | Применять только более новую версию | Нужно знать область действия версии |
| Только уникальный ID события | Убрать дубли и запросить актуальный объект | ID не задаёт хронологию |
| Время изменения | Сверять время и правила переходов | Нужна обработка равных меток и особенностей часов |
| Только уведомление об изменении | Перечитать текущее состояние через API | Потребуются лимиты запросов и объединение сигналов |
Версия объекта важнее последнего пришедшего сообщения
Если источник гарантирует возрастающую версию для конкретного объекта, сохраните её рядом с локальным состоянием. Изменяйте запись условно: входящая версия должна быть новее сохранённой. Проверка и обновление выполняются согласованно, иначе два параллельных обработчика снова создадут гонку. Отклонённое как устаревшее событие остаётся в журнале, но не переписывает актуальные поля.
Уникальный ID уведомления нужен отдельно. Он позволяет понять, что одно и то же событие уже обрабатывалось. Однако дедупликация не решает порядок разных событий. «Оплачен» и «создан» имеют разные ID, и оба могут быть совершенно корректными уведомлениями.
Если версии нет: перечитать объект или ограничить переход
Когда вебхук содержит лишь сигнал об изменении, безопаснее получить актуальное состояние объекта у источника. Несколько сигналов по одному объекту можно объединять в короткое контролируемое окно, чтобы не исчерпать лимит API. После чтения всё равно учитывайте возможность очередного изменения: полезна регулярная сверка, а не только реакция на уведомления.
В некоторых процессах помогает таблица разрешённых переходов. Например, техническое уведомление «создан» не должно отменять подтверждённую оплату. Но запрет на любое движение назад тоже ошибочен: возврат, отмена и восстановление — самостоятельные бизнес-события. Их надо моделировать явно, а не объявлять любой «меньший» статус устаревшим.
Что сохранить для разбора
Храните идентификатор события, объект, заявленную версию, время получения, принятое решение и ссылку на актуальную локальную запись. Полный исходный payload сохраняют только при обоснованной необходимости, с ограничением доступа и срока хранения. Для диагностики обычно важнее увидеть «версия 14 отклонена, потому что уже применена 16», чем читать все контактные данные покупателя.
Применение события, финансовое действие и отправка письма должны иметь собственные защиты от повторов. Отсутствие повторного изменения статуса не означает, что письмо или выплата не запустятся второй раз из побочного обработчика.
Проверьте сценарий перестановкой, а не обычной отправкой
Возьмите учебную цепочку версий 10, 11, 12 и доставьте её как 12, 10, 11, 12. Итог должен соответствовать 12, а повтор не должен повторять побочные действия. Затем добавьте разрешённое событие возврата с новой версией. Оно должно примениться, хотя название статуса визуально похоже на «откат».
Симулятор ниже показывает только правило монотонной версии одного объекта. Он не подходит источникам без такой гарантии. Его задача — сделать спор о порядке воспроизводимым: команда видит, какие версии приняты, какие пропущены и почему.
Симулятор доставки версий одного объекта
Применяется только при документированной возрастающей версии объекта у источника. Расчёт выполняется на вашем устройстве, введённые данные никуда не отправляются.
Материалы для проверки
Если нужна реализация
Состав работ зависит от текущего проекта и подтверждённой причины. Можно обсудить диагностику или разработку подходящего решения.
