IOSOR База знаний

Webhook inbound SMS: ретраи, порядок событий и идемпотентность на приёме

Руководство по разработке для B2B-команд, обрабатывающих входящие SMS: почему случаются ретраи, почему порядок событий не гарантирован, и как сделать приёмный endpoint идемпотентным вместо дублирования переписки и обработки STOP.

Каждый обработчик входящих сообщений рано или поздно сталкивается с одними и теми же тремя сюрпризами: один и тот же webhook срабатывает дважды, событие «delivered» приходит после события «failed», которое оно должно было заменить, а ответ клиента STOP обрабатывается дважды, потому что два сервера подхватили один и тот же ретрай. Ни одно из этого не является багом платформы, отправляющей webhook, — это нормальное поведение любой системы доставки «at-least-once», и ваш приёмный endpoint должен быть построен с учётом этой реальности с первого дня.

Почему webhooks вообще делают ретраи

Провайдер webhook не может быть уверен, что ваш endpoint обработал доставку. Ваш сервер может вернуть 200 после коммита в базу данных, которая затем откатывается; балансировщик нагрузки может потерять ответ на обратном пути, хотя ваш обработчик успешно завершился; деплой может перезапустить процесс в середине запроса.

Три режима сбоя, для которых нужно проектировать

Режим сбоя Что происходит Что ломается при игнорировании
Дублирующая доставка Один и тот же ID события приходит 2+ раза Дважды посчитанные ответы, дублированная обработка STOP, дублирующиеся треды переписки
События вне порядка Событие с более поздней меткой времени приходит раньше более раннего Статус «delivered» перезаписывается обратно на «sent»
Частичный/неоднозначный сбой

Идемпотентность: одно свойство, которое решает все три проблемы

Идемпотентный приёмный endpoint даёт одно и то же конечное состояние независимо от того, сколько раз доставлено одно и то же событие. Механизм прост и хорошо изучен: каждое входящее событие несёт уникальный ID события; перед обработкой вы проверяете, записывали ли вы уже этот ID; если да, вы возвращаете успех немедленно без повторной обработки. 1.

Порядок событий: почему «последняя запись побеждает» опасно

Webhook-события для одного и того же сообщения не гарантированно приходят в порядке, в котором они произошли. Ретрай более раннего события «queued» может прийти после более позднего события «delivered» из-за сетевого джиттера, очередей на стороне провайдера или того, что ваш собственный пул воркеров обрабатывает запросы не по порядку.

STOP, HELP и другие входящие ключевые слова требуют той же дисциплины

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

Начните с IOSOR

Выгрузите логи входящих webhook за неделю и посчитайте ID событий, пришедшие больше одного раза. Проиграйте один дубль и одну пару не по порядку (failed, затем delivered). Приёмник держит один эффект: одна строка inbox, одна запись STOP, одно касание кошелька. Last-write-wins, который откатывает STOP, — провал. Это идемпотентность на приёме и порядок ретраев, не проверка подписи и не замок шлюза до очереди.

Итог IOSOR

Входящие webhook делают ретраи. Идемпотентность на приёме — единственный безопасный ответ; порядок не обещан.

Делайте: ключуйте событие и игнорируйте близнеца. Не делайте: last-write-wins на STOP или два списания за одно событие.

Был ли материал полезен?

Связанные гайды