Webhook статусов SMS: подпись, повторы и идемпотентность
Как принимать статусы SMS без потерь и дублей: проверка подписи, идемпотентность, повторы, порядок событий, очередь ошибок и сверка с провайдером.

Содержание
Надёжный webhook статусов SMS должен считать повторную доставку нормальным событием, а не исключением. Приёмник сначала проверяет подлинность запроса, сохраняет событие с уникальным идентификатором и быстро отвечает 2xx; бизнес-обработка выполняется асинхронно. Такая схема защищает систему от потерь при кратковременных сбоях, дублей после повторной отправки и неправильного порядка DLR.
Почему одного HTTP-обработчика недостаточно
Delivery Receipt проходит через несколько систем. Любая из них может повторить запрос после тайм-аута, даже если ваша сторона уже успела записать результат. События submitted, delivered и failed также могут прийти не по порядку. Если обработчик сразу меняет заказ, начисляет бонус или отправляет резервное уведомление, один повтор способен запустить бизнес-действие дважды.
Поэтому webhook — это граница интеграции, а не место для всей логики. Его задача ограничена четырьмя операциями:
- принять запрос и проверить формат;
- подтвердить, что запрос пришёл от ожидаемой стороны;
- надёжно сохранить исходное событие;
- вернуть ответ до истечения тайм-аута отправителя.
Какие поля нужны событию
Согласуйте контракт до запуска. Практический минимум:
- идентификатор сообщения на стороне клиента;
- идентификатор платформы или маршрута;
- уникальный идентификатор события;
- исходный и нормализованный статус;
- время события у источника и время получения;
- код и описание ошибки, если они есть;
- версия схемы;
- идентификатор попытки доставки webhook;
- подпись и временная метка запроса.
Номер телефона и текст SMS обычно не нужны обработчику статусов. Если без номера не обойтись, ограничьте хранение и доступ, а в журналах используйте маскирование. Связать DLR с заказом или авторизацией лучше через непрозрачный correlation ID.
Проверка подписи и защита от повтора
Разрешение IP-адресов полезно как дополнительный слой, но не подтверждает целостность тела запроса. Основой должна быть криптографическая подпись: например, HMAC от необработанного тела, временной метки и идентификатора события. Проверяйте подпись до разбора бизнес-полей и сравнивайте значения константным по времени способом.
Временная метка ограничивает окно повторного воспроизведения. Запросы со слишком старым временем отклоняются, а идентификатор события сохраняется в таблице дедупликации. Секрет подписи должен храниться отдельно от кода и поддерживать ротацию с коротким периодом одновременного действия старого и нового ключа.
Не записывайте подписи, секреты и полные номера в обычный application log. Для расследования достаточно request ID, результата проверки, версии ключа и хеша полезной нагрузки.
Идемпотентность: один факт — одно изменение
Одинаковое событие может быть доставлено несколько раз. Создайте уникальное ограничение по provider + event_id или другому подтверждённо стабильному ключу. Вставка события и фиксация факта приёма должны происходить атомарно.
Если поставщик не выдаёт event ID, составной ключ можно построить из идентификатора сообщения, статуса, времени источника и кода ошибки. Такой вариант слабее: правила округления времени и нормализации должны быть зафиксированы, иначе разные события могут ошибочно слиться.
Идемпотентность нужна и на уровне бизнес-операции. Переход submitted → delivered допустим один раз, а повторный delivered не должен повторно закрывать заказ. Для побочных действий используйте outbox: в одной транзакции сохраните новое состояние и команду на последующую обработку.
Что делать с событиями не по порядку
Не полагайтесь на время получения. Храните журнал событий и отдельно вычисляемое текущее состояние сообщения. Правила переходов должны учитывать финальность статуса и временную метку источника.
Например, поздний submitted не должен заменить уже полученный delivered. Но более новый финальный статус может потребовать уточнения, если поставщик допускает корректировку результата. Такие правила нельзя угадывать: зафиксируйте таблицу переходов вместе с набором DLR, о котором подробно рассказано в материале про метрики доставки SMS.
Неизвестный статус сохраняйте без потери исходного значения и направляйте в отдельную категорию наблюдения. Молчаливое преобразование неизвестного кода в failed искажает аналитику и может преждевременно включить резервный канал.
Повторы, backoff и очередь ошибок
Webhook-провайдер обычно повторяет доставку при тайм-ауте или ответе не 2xx. Поэтому приёмник должен отвечать быстро — после надёжной записи, но до тяжёлой обработки. Если база или брокер недоступны и событие не сохранено, возвращайте ошибку: ложный 200 OK превращает временный сбой в безвозвратную потерю.
Внутренний обработчик использует ограниченное число повторов с экспоненциальной задержкой и jitter. Повторять без паузы опасно: сбой зависимости превращается в лавину запросов. После исчерпания попыток событие помещается в dead-letter queue с причиной, числом попыток и инструкцией по безопасному переигрыванию.
Переигрывание не должно обходить дедупликацию. Оператор выбирает диапазон событий, запускает тот же обработчик и видит результат каждой записи.
Сверка закрывает тихие потери
Даже хорошая доставка webhook не доказывает полноту истории. Нужен периодический процесс сверки:
- выбрать сообщения без финального статуса старше контрольного интервала;
- запросить состояние через API или выгрузку платформы;
- сравнить количество и распределение статусов по периоду;
- восстановить отсутствующие события через тот же идемпотентный путь;
- зафиксировать причину расхождения.
Сверка особенно важна после аварии, ротации ключей или изменения контракта. Она дополняет, а не заменяет webhook.
Метрики и алерты
Контролируйте не только долю ответов 2xx, но и весь путь:
- задержку от времени события до приёма и обработки;
- число повторных доставок и долю дублей;
- ошибки подписи и запросы вне допустимого окна;
- размер очереди и возраст самого старого события;
- количество записей в dead-letter queue;
- сообщения без финального статуса;
- неизвестные коды и запрещённые переходы состояния.
Разделяйте технические ошибки webhook и недоставку самой SMS. Первый показатель описывает вашу интеграцию, второй — маршрут связи. Общие принципы алертов и целевых показателей есть в руководстве по SLI, SLO и бюджету ошибок.
Чек-лист приёмки
Перед промышленным запуском воспроизведите:
- два одинаковых события подряд;
delivered, пришедший раньшеsubmitted;- неверную подпись и просроченную временную метку;
- тайм-аут после успешной записи;
- недоступность базы и брокера;
- неизвестный статус и новую версию схемы;
- исчерпание повторов и переигрывание из DLQ;
- сверку сообщения, webhook которого был потерян.
Измеряйте время ответа под пиковой нагрузкой и проверяйте, что один медленный потребитель не блокирует весь входящий поток. Общую границу ответственности API полезно сверить с материалом про устойчивость внешних API.
Практический вывод
Webhook DLR становится надёжным, когда повтор безопасен, порядок событий не предполагается, а каждое принятое сообщение можно проследить от сырого запроса до бизнес-состояния. Подпись защищает доверие к источнику, идемпотентность — от дублей, очередь — от кратковременных сбоев, а сверка — от тихих потерь.
QuickTel предоставляет API и статусы для корпоративных SMS. Перед интеграцией согласуйте формат DLR, правила подписи, тайм-ауты, повторы и способ сверки, а затем проверьте контракт на собственном тестовом контуре.


