Rastreamento

Webhooks de entrega com assinaturas

Defina um endpoint HTTPS na página Webhooks do console. Quando uma mensagem atinge um status final, enviamos um POST com um evento JSON para esse endpoint.

Eventos

typeQuando
message.deliveredA mensagem foi entregue.
message.failedA mensagem falhou; data.error contém o código. Seu valor foi reembolsado.
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"
  }
}

Cabeçalhos e assinatura

Cada chamada possui três cabeçalhos, no formato Standard Webhooks:

CabeçalhoValor
webhook-idO ID do evento (evt_...). O mesmo ID é usado para cada nova tentativa de um evento.
webhook-timestampHorário Unix em segundos quando esta tentativa foi assinada.
webhook-signaturev1, seguido pela assinatura base64 HMAC-SHA256. Várias assinaturas podem ser listadas, separadas por espaços: aceite a chamada quando qualquer uma corresponder.

Para verificar uma chamada: pegue o segredo do console (ele começa com whsec_), decodifique a parte após whsec_ usando base64url para obter a chave, calcule o HMAC-SHA256 de webhook-id + "." + webhook-timestamp + "." + raw body, codifique-o em base64 e compare-o com a assinatura em tempo constante. Rejeite timestamps com mais de cinco minutos de diferença do seu relógio. Sempre use o corpo bruto: um corpo JSON analisado e reserializado não corresponderá.

// 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);

Resposta e tentativas

  • Responda com qualquer status 2xx em até 10 segundos. Faça o trabalho lento após responder, a partir de uma fila.
  • Redirecionamentos não são seguidos: aponte a configuração para o endereço final.
  • Uma chamada que expira ou responde com algo diferente de 2xx é tentada novamente após 30 segundos, 2 minutos, 10 minutos, 30 minutos, 1 hora, 2 horas, 4 horas, 8 horas, 8 horas: 10 tentativas no total, cerca de 24 horas a partir da primeira.
  • Cada tentativa é listada na página de Webhooks com o status HTTP e o tempo que levou.

Duplicatas e ordem

Um evento pode chegar duas vezes, por exemplo, quando seu endpoint o salvou, mas a resposta foi perdida. Armazene webhook-id e pule IDs que você já processou. Eventos para diferentes mensagens podem chegar em qualquer ordem; leia data.status e data.done_at em vez de confiar na ordem de chegada.

Regras de endpoint

  • Apenas HTTPS, em um nome de host público (sem literais de IP, localhost ou redes privadas).
  • O segredo pode ser substituído na página de Webhooks a qualquer momento; chamadas assinadas após isso usam o novo segredo.
  • Remover o endpoint interrompe novos eventos; mensagens que já foram concluídas mantêm seu status para polling.
Webhooks de entrega com assinaturas | Documentação BulkSMSAPI