Отслеживание

Webhooks доставки с подписями

Установите HTTPS endpoint на странице Webhooks в консоли. Когда сообщение достигает финального статуса, мы отправляем один POST с JSON событием на этот endpoint.

События

typeКогда
message.deliveredСообщение было доставлено.
message.failedСообщение не доставлено; data.error содержит код ошибки. Его стоимость возвращена.
POST to your endpointJSON
{
  "id": "evt_8F3K2Q9TZ1MXD",
  "type": "message.delivered",
  "created_at": "2026-11-07T09:30:17.902Z",
  "data": {
    "id": "msg_8F3K2Q9TZ1MX",
    "object": "message",
    "batch_id": "bat_4M7Q1X0PK2RS",
    "to": "+14155550123",
    "country": "US",
    "text": "Your Northwind code is 482913. It expires in 10 minutes.",
    "segments": 1,
    "price": "0.00507",
    "currency": "USD",
    "status": "delivered",
    "error": null,
    "reference": "login-7781",
    "created_at": "2026-11-07T09:30:12.418Z",
    "sent_at": "2026-11-07T09:30:13.020Z",
    "done_at": "2026-11-07T09:30:17.902Z"
  }
}

Заголовки и подпись

Каждый вызов содержит три заголовка в формате Standard Webhooks:

ЗаголовокЗначение
webhook-idИдентификатор события (evt_...). Один и тот же идентификатор используется для каждой повторной попытки одного события.
webhook-timestampUnix-время в секундах, когда эта попытка была подписана.
webhook-signaturev1, за которым следует подпись base64 HMAC-SHA256. Может быть указано несколько подписей, разделенных пробелами: принимайте вызов, если любая из них совпадает.

Для проверки вызова: возьмите секрет из консоли (он начинается с whsec_), декодируйте часть после whsec_ с помощью base64url, чтобы получить ключ, вычислите HMAC-SHA256 от webhook-id + "." + webhook-timestamp + "." + raw body, закодируйте его в base64 и сравните с подписью за постоянное время. Отклоняйте временные метки, которые отличаются от ваших часов более чем на пять минут. Всегда используйте исходное тело: разобранное и повторно сериализованное JSON-тело не совпадет.

// Express: keep the raw body, check the signature, then answer 2xx quickly
import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.BULKSMSAPI_WEBHOOK_SECRET; // whsec_...

app.post("/hooks/sms", express.raw({ type: "application/json" }), (req, res) => {
  const id = req.header("webhook-id");
  const ts = req.header("webhook-timestamp");
  const key = Buffer.from(secret.slice("whsec_".length), "base64url");
  const expected = crypto.createHmac("sha256", key).update(`${id}.${ts}.${req.body}`).digest("base64");
  const sent = (req.header("webhook-signature") || "").split(" ").map((s) => s.replace(/^v1,/, ""));
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  if (!fresh || !sent.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body);
  console.log(event.type, event.data.id, event.data.status);
  res.status(204).end();
});
app.listen(3000);

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

  • Ответьте с любым статусом 2xx в течение 10 секунд. Выполняйте медленную работу после ответа, из очереди.
  • Перенаправления не отслеживаются: указывайте настройку на конечный адрес.
  • Вызов, который завершается тайм-аутом или отвечает чем-либо, кроме 2xx, повторяется через 30 секунд, 2 минуты, 10 минут, 30 минут, 1 час, 2 часа, 4 часа, 8 часов, 8 часов: всего 10 попыток, около 24 часов с первой.
  • Каждая попытка перечислена на странице Webhooks с HTTP-статусом и временем, которое она заняла.

Дубликаты и порядок

Событие может прийти дважды, например, когда ваш конечный пункт сохранил его, но ответ был потерян. Сохраняйте webhook-id и пропускайте идентификаторы, которые вы уже обработали. События для разных сообщений могут приходить в любом порядке; читайте data.status и data.done_at, а не полагайтесь на порядок прибытия.

Правила endpoint

  • Только HTTPS, на публичном имени хоста (без IP-литералов, localhost или частных сетей).
  • Секрет можно заменить на странице Webhooks в любое время; вызовы, подписанные после этого, используют новый секрет.
  • Удаление конечного пункта останавливает новые события; сообщения, которые уже завершены, сохраняют свой статус для опроса.
Webhooks доставки с подписями | Документация BulkSMSAPI