Вебхуки
PushData может отправлять POST на ваш сервер при каждом событии с уведомлением или прогоном сценария: провайдер принял push, доставка не удалась, получатель открыл или кликнул, прогон завершился. Вебхуки настраиваются на приложение в Приложения → Вебхуки (или мутациями createHook / updateHook / deleteHook).
События
| Событие | Когда | payload |
|---|---|---|
NOTIFICATION_SENT | Провайдер принял доставку одному получателю | notificationId, deviceId или channelId, messageId |
NOTIFICATION_FAILED | Доставка одному получателю окончательно не удалась (после повторов транзиентных ошибок) | notificationId, deviceId или channelId, error |
NOTIFICATION_DELIVERED | Клиент подтвердил доставку (POST /api/v1/track, service worker, мобильные SDK); только push | notificationId, deliveryLogId, deliveredAt |
NOTIFICATION_OPENED | Загружен пиксель открытия письма или клиент сообщил об открытии | notificationId, deliveryLogId, openedAt |
NOTIFICATION_CLICKED | Переход по отслеживаемой ссылке или клиент сообщил о клике | notificationId, deliveryLogId, clickedAt, destination |
WORKFLOW_COMPLETED | Прогон сценария выполнил последний шаг | executionId, workflowId, status |
WORKFLOW_FILTERED | Шаг FILTER остановил прогон | executionId, workflowId, status, stepOrder |
WORKFLOW_FAILED | Шаг упал окончательно (ошибка провайдера, квота, выключенное приложение) | executionId, workflowId, error |
WORKFLOW_CANCELLED | Прогон отменён (оператором, архивированием или удалением сценария) | executionId, workflowId, reason |
Вебхук подписывается на список событий; пустой список означает все события. Неизвестные имена событий отклоняются при создании (400). Одно событие доставляется каждому активному вебхуку приложения, подписанному на него: рассылка на 10 000 устройств с двумя вебхуками на NOTIFICATION_SENT — это 20 000 POST-запросов.
Запрос
POST https://your-server.example/pushdata
Content-Type: application/json
X-PushData-Signature: t=1757664000, v1=5f1c…e3a9{
"id": "019c9a99-8aad-77b4-b2b2-719f3f8e0610",
"event": "NOTIFICATION_SENT",
"appId": "019c9359-fe1d-73bc-90fd-594d284d24f3",
"timestamp": "2026-09-12T08:00:00.000Z",
"payload": { "notificationId": "…", "deviceId": "…", "messageId": "…" }
}idидентифицирует событие и не меняется между повторами — используйте его для дедупликации.timestamp— момент события, а не отправки этой попытки.- Тело ответа не читается; учитывается только статус.
Отвечайте 2xx в течение 10 секунд. Всё остальное (4xx, 5xx, таймаут, отказ соединения, редирект) — неудачная попытка.
Подпись
Если у вебхука задан секрет, каждый запрос подписывается по схеме Stripe: X-PushData-Signature: t=<unix-секунды>, v1=<hex HMAC-SHA256>, где ключ HMAC — секрет, а подписываемый текст — <t>.<сырое тело запроса>. Метка времени свежая на каждой попытке: сверяйте её со своими часами (принято окно в 5 минут) и сравнивайте подпись за постоянное время:
import { Buffer } from 'node:buffer'
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyPushDataWebhook(rawBody: string, header: string, secret: string, toleranceSeconds = 300): boolean {
const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')))
const t = Number(parts.t)
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds)
return false
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const given = String(parts.v1 ?? '')
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected))
}Проверяйте по сырым байтам тела, до разбора JSON и любой пересериализации. Заголовок один — X-PushData-Signature; получатель, написанный под имя до ребрендинга (X-Nitroping-Signature), должен читать новое имя.
Повторы и автоматический выключатель
- Неудачная попытка повторяется с экспоненциальной задержкой: 6 попыток, начиная с 30 секунд, всего около 15 минут. Каждая попытка несёт свежую метку времени в подписи.
- Вебхук, у которого 50 доставок подряд не удались, выключается; дашборд показывает причину рядом со статусом, в API её несёт
Hook.disabledReason. Успешная доставка сбрасывает счётчик. Почините endpoint и нажмите Включить (илиupdateHook(isActive: true)) — это же сбрасывает выключатель. - Доставки, исчерпавшие повторы, записываются в Dead letters для разбора (переиграть оттуда можно только отправки уведомлений; вебхук просто уйдёт со следующим событием).
- У приложения может быть не более 20 вебхуков.
Ограничения
- URL должен быть публичным адресом
http(s): loopback, приватные и link-local диапазоны отклоняются при создании и ещё раз при доставке (адрес резолвится и закрепляется, редиректы не выполняются). - Имя до 120 символов, секрет до 256; секрет хранится зашифрованным и после создания больше не показывается.
- Порядок между событиями не гарантируется (каждая доставка — отдельная джоба); опирайтесь на
timestampи собственное состояние.