Документация / Вебхуки

Вебхуки

Вебхук — это HTTP-запрос, который сервис отправляет вашему приложению, когда с письмом что-то происходит: оно доставлено, отклонено, отложено, открыто или получатель отписался. Так приложение узнаёт о судьбе письма, не опрашивая API.

Как это работает

  1. Вы создаёте для домена один или несколько эндпоинтов и выбираете, какие события каждый получает.
  2. При наступлении события сервис отправляет POST с JSON на ваш URL и подписывает запрос секретом эндпоинта.
  3. Приложение проверяет подпись, отвечает 2xx и обрабатывает событие.

Настройка

  • Добавьте эндпоинт — укажите URL, выберите события и, при желании, описание. Эндпоинт включается сразу.
  • Сохраните секрет подписи — значение вида whsec_… создаётся вместе с эндпоинтом и показывается только один раз. Скопируйте его сразу и храните как пароль.
  • Проверьте связь — кнопка отправки тестового события в панели покажет, доходит ли запрос и что ответил ваш сервер.

События

СобытиеКогда приходит
deliveredсервер получателя принял письмо
deferredвременный отказ, доставка будет повторена
bouncedпостоянный отказ, доставка невозможна
complainedполучатель пожаловался на спам
droppedписьмо не отправлено — например, адрес в списке исключений
openedписьмо открыли
unsubscribedполучатель отписался

Формат запроса

json
{
  "event_id": "a1b2c3d4-0001-4f3a-9c2e-7b6d5e4f3a21",
  "event": "delivered",
  "domain": "mail.example.ru",
  "recipient": "client@example.ru",
  "message_id": "01JN4K9F2A",
  "occurred_at": "2026-06-24T09:41:13.512000+00:00"
}

Заголовки:

X-Webhook-Event: delivered
X-Webhook-Delivery: a1b2c3d4-0001-4f3a-9c2e-7b6d5e4f3a21
X-Webhook-Timestamp: 1782639673
X-Webhook-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

Проверка подписи

Подпись — HMAC-SHA256 от строки {timestamp}.{тело запроса}, где тело берётся в исходном виде, до разбора JSON. Ключ — секрет эндпоинта.

verify.pypython
import hmac
import hashlib

WEBHOOK_SECRET = "whsec_..."  # секрет эндпоинта из панели

def is_valid(headers, raw_body: bytes) -> bool:
    signature = headers["X-Webhook-Signature"]
    timestamp = headers["X-Webhook-Timestamp"]

    # 1. Отбросьте запросы со старой меткой времени — защита от повторов.
    #    (сравните timestamp с текущим временем, допуск в несколько минут)

    # 2. Пересчитайте подпись по строке "{timestamp}.{raw_body}".
    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(
        WEBHOOK_SECRET.encode(), signed, hashlib.sha256
    ).hexdigest()

    # 3. Сравнивайте за постоянное время.
    return hmac.compare_digest(expected, signature)
Подпись считается по тем самым байтам, которые пришли. Если фреймворк сначала разберёт JSON, а вы потом соберёте его обратно, порядок ключей или пробелы могут измениться — и подпись не сойдётся.

Ответ и повторы

  • Отвечайте 2xx сразу, а обработку выполняйте асинхронно — долгий ответ считается неудачей.
  • Если ответ не получен или он не 2xx, доставка повторяется; время следующей попытки видно в панели.
  • Одно и то же событие может прийти дважды. Используйте event_id для защиты от повторной обработки.