Webhook — это HTTP-callback от сервера к серверу: платформа сообщений выполняет POST события на URL потребителя. Надёжная доставка работает по принципу at-least-once, поэтому производители повторяют неудавшиеся доставки с экспоненциальной выдержкой и джиттером, отправляют в dead-letter после лимита, а потребители обязаны быть идемпотентными, поскольку дубликаты и события вне порядка ожидаемы.
- Ваше приложениеОтправка через REST API или SMPP
- Платформа BroadNetВалидация, маршрутизация и тарификация каждого сообщения
- SMSC оператораПрямое соединение с оператором
- Телефон абонентаСообщение доставлено
Архитектура webhook
Webhook меняет местами обычные роли клиента и сервера: вместо того чтобы приложение опрашивало платформу сообщений на предмет обновлений, платформа выполняет POST события на URL, который зарегистрировало приложение, — изменение статуса доставки, входящее сообщение, событие аккаунта. Это push-половина асинхронной модели, pull-половиной которой является опрос GET /messages/{id}. Эндпоинт потребителя — это обычный HTTPS-сервер, который должен принять POST, сделать минимум, необходимый для надёжной записи события, и быстро вернуть 2xx. Распространённый и важный паттерн — подтвердить приём и обработать асинхронно: эндпоинт записывает сырое событие в очередь или журнал, возвращает 200, а отдельный воркер выполняет реальную работу — так медленная бизнес-логика никогда не заставит производителя выйти за тайм-аут и повторить.
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"
}Гарантии доставки: at-least-once
Распределённая доставка не может быть одновременно exactly-once и доступной, поэтому почти каждая продакшн-система webhook выбирает at-least-once: производитель повторяет попытки, пока не увидит подтверждение об успехе, принимая, что потребитель иногда может получить одно и то же событие более одного раза. Альтернативные крайности хуже — at-most-once теряет события при первом сбое, а истинный exactly-once требует распределённой координации, которую транспорт HTTP не предоставляет. At-least-once возлагает одну обязанность на потребителя: он должен быть идемпотентным, дедуплицируя по стабильному идентификатору события, чтобы повторно доставленное событие было безвредным. Контракт подтверждения прост: любой 2xx означает «прекратить повторы»; всё остальное (или тайм-аут) означает «повторить позже».
Повторы с экспоненциальной выдержкой и джиттером
Когда доставка не удаётся или истекает по тайм-ауту, производитель повторяет по расписанию, растущему экспоненциально, — базовая задержка удваивается на каждой попытке до потолка, — чтобы кратковременно недоступный эндпоинт не забрасывали. Важно, что задержка включает случайный джиттер: без него множество событий, отказавших во время одного и того же сбоя, повторяли бы попытки в унисон и создали бы синхронизированное «стадо» в момент восстановления эндпоинта. Полный джиттер (случайное значение между нулём и текущей границей выдержки) равномерно распределяет эту нагрузку. Повторы продолжаются, пока событие не будет успешным или не будет достигнут общий лимит попыток либо времени, после чего событие отправляется в dead-letter, а не повторяется вечно.
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Тайм-ауты и порядок
Производитель устанавливает короткий тайм-аут на каждую доставку — обычно несколько секунд — и трактует тайм-аут так же, как сбой: соединение разрывается, а событие ставится в план на повтор. Это механическая причина, по которой потребителям следует подтверждать быстро и обрабатывать асинхронно. Порядок — более сложная гарантия. Поскольку повторы и параллельная доставка чередуются, наивный поток webhook упорядочен лишь по возможности: повторённое более раннее событие может прийти после более позднего. Системы, которым нужен порядок, либо доставляют по ключу последовательно (все события для одного message id на одной упорядоченной партиции, с блокировкой при сбое), либо, что более распространено, делают полезную нагрузку самоупорядоченной — каждое событие несёт метку времени и монотонную последовательность, чтобы потребитель мог отбросить устаревшее обновление, а не зависеть от порядка прибытия.
| Стратегия | Как это работает | Стоимость |
|---|---|---|
| Best-effort | Доставлять по мере готовности, повторять независимо | События могут приходить не по порядку |
| Упорядочение по ключу | Последовательно обрабатывать все события для одного идентификатора; блокировать выполнение при сбое | Блокировка начала очереди (head-of-line), снижение пропускной способности |
| Самоупорядочивающаяся полезная нагрузка | Передаются метка времени + порядковый номер; потребитель отбрасывает устаревшие | Логика на стороне потребителя, но без блокировки |
Dead-letter и здоровье эндпоинта
Эндпоинт, остающийся недоступным, не должен вечно потреблять ёмкость повторов. После исчерпания политики повторов событие перемещается в очередь dead-letter — надёжное хранилище недоставленных событий, которое оператор может изучить и воспроизвести после починки эндпоинта, — а не молча отбрасывается. Чтобы защитить обе стороны, пока эндпоинт нездоров, производители отслеживают здоровье доставки по каждому эндпоинту и применяют размыкание цепи: после устойчивой череды сбоев цепь размыкается, и новые события паркуются или быстро отклоняются вместо попыток, а затем полуоткрытая проба периодически проверяет восстановление, прежде чем возобновится нормальная доставка. Это не даёт мёртвому потребителю порождать очередь обречённых попыток и позволяет восстанавливающемуся плавно набрать обороты. Конкретное поведение webhook и повторов BroadNet задокументировано для разработчиков.
Построение идемпотентного потребителя
Поскольку доставка at-least-once гарантирует дубликаты, эффекты exactly-once принадлежат потребителю, а не производителю. Паттерн — хранилище дедупликации по стабильному, предоставленному производителем идентификатору события (Webhook-Id или event id, никогда не только по хешу полезной нагрузки). При приёме потребитель проверяет, обрабатывал ли он уже этот id; если да — подтверждает 2xx и больше ничего не делает; если нет — записывает id и применяет эффект в той же транзакции, чтобы сбой не смог применить эффект без записи id. В сочетании с самоупорядоченными полезными нагрузками это превращает повторно доставленное или пришедшее вне порядка событие в no-op, что и делает из транспорта at-least-once бизнес-результаты exactly-once.
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?
Иначе стабильно отказывающий эндпоинт потреблял бы ёмкость повторов обречёнными попытками и накапливал бы очередь. Размыкание цепи срабатывает после устойчивых сбоев, паркуя или быстро отклоняя новые события, а затем использует полуоткрытую пробу для проверки восстановления перед возобновлением нормальной доставки — защищая и производителя, и восстанавливающегося потребителя.
Технические характеристики источника
На этой странице представлены опубликованные стандарты, на которых они основаны. (Открывается в новой вкладке)