BroadNet
HTTP API & webhooksWebhook

Доставка и надёжность webhook: повторы, порядок, dead-letter

Обновлено 5 мин чтенияна основе стандартов
Прямой ответ

Webhook — это HTTP-callback от сервера к серверу: платформа сообщений выполняет POST события на URL потребителя. Надёжная доставка работает по принципу at-least-once, поэтому производители повторяют неудавшиеся доставки с экспоненциальной выдержкой и джиттером, отправляют в dead-letter после лимита, а потребители обязаны быть идемпотентными, поскольку дубликаты и события вне порядка ожидаемы.

СхемаКак сообщение A2P доходит до телефона
  1. Ваше приложениеОтправка через REST API или SMPP
  2. Платформа BroadNetВалидация, маршрутизация и тарификация каждого сообщения
  3. SMSC оператораПрямое соединение с оператором
  4. Телефон абонентаСообщение доставлено
Отчет о доставке (DLR) возвращается по тому же пути на ваш webhook
§01

Архитектура webhook

Webhook меняет местами обычные роли клиента и сервера: вместо того чтобы приложение опрашивало платформу сообщений на предмет обновлений, платформа выполняет POST события на URL, который зарегистрировало приложение, — изменение статуса доставки, входящее сообщение, событие аккаунта. Это push-половина асинхронной модели, pull-половиной которой является опрос GET /messages/{id}. Эндпоинт потребителя — это обычный HTTPS-сервер, который должен принять POST, сделать минимум, необходимый для надёжной записи события, и быстро вернуть 2xx. Распространённый и важный паттерн — подтвердить приём и обработать асинхронно: эндпоинт записывает сырое событие в очередь или журнал, возвращает 200, а отдельный воркер выполняет реальную работу — так медленная бизнес-логика никогда не заставит производителя выйти за тайм-аут и повторить.

POST webhook о статусе доставки потребителюhttp
POST /webhooks/dlr HTTP/1.1
Host: app.example
Content-Type: application/json
Webhook-Id: 8b1a...
Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b...

{
  "event": "message.delivered",
  "message_id": "01HZY8Q3M6",
  "status": "delivered",
  "occurred_at": "2026-07-19T09:15:25Z"
}
§02

Гарантии доставки: at-least-once

Распределённая доставка не может быть одновременно exactly-once и доступной, поэтому почти каждая продакшн-система webhook выбирает at-least-once: производитель повторяет попытки, пока не увидит подтверждение об успехе, принимая, что потребитель иногда может получить одно и то же событие более одного раза. Альтернативные крайности хуже — at-most-once теряет события при первом сбое, а истинный exactly-once требует распределённой координации, которую транспорт HTTP не предоставляет. At-least-once возлагает одну обязанность на потребителя: он должен быть идемпотентным, дедуплицируя по стабильному идентификатору события, чтобы повторно доставленное событие было безвредным. Контракт подтверждения прост: любой 2xx означает «прекратить повторы»; всё остальное (или тайм-аут) означает «повторить позже».

§03

Повторы с экспоненциальной выдержкой и джиттером

Когда доставка не удаётся или истекает по тайм-ауту, производитель повторяет по расписанию, растущему экспоненциально, — базовая задержка удваивается на каждой попытке до потолка, — чтобы кратковременно недоступный эндпоинт не забрасывали. Важно, что задержка включает случайный джиттер: без него множество событий, отказавших во время одного и того же сбоя, повторяли бы попытки в унисон и создали бы синхронизированное «стадо» в момент восстановления эндпоинта. Полный джиттер (случайное значение между нулём и текущей границей выдержки) равномерно распределяет эту нагрузку. Повторы продолжаются, пока событие не будет успешным или не будет достигнут общий лимит попыток либо времени, после чего событие отправляется в dead-letter, а не повторяется вечно.

Экспоненциальная выдержка с полным джиттеромtext
base = 2s ; factor = 2 ; cap = 3600s
attempt n bound = min(cap, base * factor^(n-1))
delay(n)        = random(0, bound)      # full jitter

n=1  bound  2s    delay in [0, 2s]
n=2  bound  4s    delay in [0, 4s]
n=3  bound  8s    delay in [0, 8s]
n=4  bound 16s    delay in [0, 16s]
...  until success or attempt/time cap -> dead-letter
§04

Тайм-ауты и порядок

Производитель устанавливает короткий тайм-аут на каждую доставку — обычно несколько секунд — и трактует тайм-аут так же, как сбой: соединение разрывается, а событие ставится в план на повтор. Это механическая причина, по которой потребителям следует подтверждать быстро и обрабатывать асинхронно. Порядок — более сложная гарантия. Поскольку повторы и параллельная доставка чередуются, наивный поток webhook упорядочен лишь по возможности: повторённое более раннее событие может прийти после более позднего. Системы, которым нужен порядок, либо доставляют по ключу последовательно (все события для одного message id на одной упорядоченной партиции, с блокировкой при сбое), либо, что более распространено, делают полезную нагрузку самоупорядоченной — каждое событие несёт метку времени и монотонную последовательность, чтобы потребитель мог отбросить устаревшее обновление, а не зависеть от порядка прибытия.

Стратегии упорядочивания потоков webhook
СтратегияКак это работаетСтоимость
Best-effortДоставлять по мере готовности, повторять независимоСобытия могут приходить не по порядку
Упорядочение по ключуПоследовательно обрабатывать все события для одного идентификатора; блокировать выполнение при сбоеБлокировка начала очереди (head-of-line), снижение пропускной способности
Самоупорядочивающаяся полезная нагрузкаПередаются метка времени + порядковый номер; потребитель отбрасывает устаревшиеЛогика на стороне потребителя, но без блокировки
§05

Dead-letter и здоровье эндпоинта

Эндпоинт, остающийся недоступным, не должен вечно потреблять ёмкость повторов. После исчерпания политики повторов событие перемещается в очередь dead-letter — надёжное хранилище недоставленных событий, которое оператор может изучить и воспроизвести после починки эндпоинта, — а не молча отбрасывается. Чтобы защитить обе стороны, пока эндпоинт нездоров, производители отслеживают здоровье доставки по каждому эндпоинту и применяют размыкание цепи: после устойчивой череды сбоев цепь размыкается, и новые события паркуются или быстро отклоняются вместо попыток, а затем полуоткрытая проба периодически проверяет восстановление, прежде чем возобновится нормальная доставка. Это не даёт мёртвому потребителю порождать очередь обречённых попыток и позволяет восстанавливающемуся плавно набрать обороты. Конкретное поведение webhook и повторов BroadNet задокументировано для разработчиков.

§06

Построение идемпотентного потребителя

Поскольку доставка at-least-once гарантирует дубликаты, эффекты exactly-once принадлежат потребителю, а не производителю. Паттерн — хранилище дедупликации по стабильному, предоставленному производителем идентификатору события (Webhook-Id или event id, никогда не только по хешу полезной нагрузки). При приёме потребитель проверяет, обрабатывал ли он уже этот id; если да — подтверждает 2xx и больше ничего не делает; если нет — записывает id и применяет эффект в той же транзакции, чтобы сбой не смог применить эффект без записи id. В сочетании с самоупорядоченными полезными нагрузками это превращает повторно доставленное или пришедшее вне порядка событие в no-op, что и делает из транспорта at-least-once бизнес-результаты exactly-once.

Идемпотентный потребитель (псевдокод)text
on POST(event):
  verify_signature(event)          # reject if invalid
  id = event.headers['Webhook-Id']
  begin transaction:
    if seen(id): return 200        # duplicate -> no-op
    if event.sequence <= last_seq(event.message_id):
        mark_seen(id); return 200  # stale -> discard
    apply_effect(event)
    mark_seen(id); set_last_seq(...)
  commit
  return 200
Часто задаваемые вопросы

Часто задаваемые вопросы

Какую гарантию доставки дают webhook?

At-least-once. Производитель повторяет, пока не увидит подтверждение 2xx, поэтому потребитель может получить одно и то же событие более одного раза. Exactly-once поверх обычного HTTP потребовал бы распределённой координации, которую транспорт не предоставляет, поэтому от потребителя ожидается идемпотентность и дедупликация по стабильному event id.

Зачем добавлять джиттер в выдержку повторов webhook?

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

Как должен отвечать эндпоинт webhook?

Быстро и кодом 2xx. Рекомендуемый паттерн — надёжно записать сырое событие, немедленно вернуть 200 и обработать асинхронно в отдельном воркере. Так медленная бизнес-логика никогда не приведёт к срабатыванию тайм-аута доставки производителя и ненужному повтору.

Доставляются ли события webhook по порядку?

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

Что такое очередь dead-letter для webhook?

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

Зачем применять размыкание цепи к эндпоинтам webhook?

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

Ссылки

Технические характеристики источника

На этой странице представлены опубликованные стандарты, на которых они основаны. (Открывается в новой вкладке)

BNПоддерживается инженерной командой BroadNet·Обмен сообщениями операторского класса с 2003 года.
Прикрепите это к проводу

Разработано с использованием реального SMPP подключения и REST API.

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