Tracciamento

Webhook di consegna con firme

Imposta un endpoint HTTPS nella pagina Webhooks della console. Quando un messaggio raggiunge uno stato finale, inviamo un POST con un evento JSON a quell'endpoint.

Eventi

typeQuando
message.deliveredIl messaggio è stato consegnato.
message.failedIl messaggio non è stato consegnato; data.error contiene il codice. Il suo prezzo è stato rimborsato.
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"
  }
}

Header e firma

Ogni chiamata include tre header, nel formato Standard Webhooks:

HeaderValore
webhook-idL'ID dell'evento (evt_...). Lo stesso ID viene utilizzato per ogni tentativo di un evento.
webhook-timestampTempo Unix in secondi quando questo tentativo è stato firmato.
webhook-signaturev1, seguito dalla firma HMAC-SHA256 in base64. Possono essere elencate diverse firme, separate da spazi: accetta la chiamata quando una qualsiasi corrisponde.

Per verificare una chiamata: prendi il segreto dalla console (inizia con whsec_), decodifica in base64url la parte dopo whsec_ per ottenere la chiave, calcola HMAC-SHA256 di webhook-id + "." + webhook-timestamp + "." + raw body, codificalo in base64 e confrontalo con la firma in tempo costante. Rifiuta i timestamp più di cinque minuti lontani dal tuo orologio. Usa sempre il corpo grezzo: un corpo JSON analizzato e riserializzato non corrisponderà.

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

Risposta e tentativi

  • Rispondi con qualsiasi stato 2xx entro 10 secondi. Esegui il lavoro lento dopo aver risposto, da una coda.
  • I reindirizzamenti non vengono seguiti: punta l'impostazione all'indirizzo finale.
  • Una chiamata che scade o risponde con qualcosa di diverso da 2xx viene ritentata dopo 30 secondi, 2 minuti, 10 minuti, 30 minuti, 1 ora, 2 ore, 4 ore, 8 ore, 8 ore: 10 tentativi in totale, circa 24 ore dal primo.
  • Ogni tentativo è elencato nella pagina Webhooks con lo stato HTTP e il tempo impiegato.

Duplicati e ordine

Un evento può arrivare due volte, ad esempio quando il tuo endpoint lo ha salvato ma la risposta è stata persa. Memorizza webhook-id e salta gli ID che hai già gestito. Gli eventi per messaggi diversi possono arrivare in qualsiasi ordine; leggi data.status e data.done_at invece di affidarti all'ordine di arrivo.

Regole dell'endpoint

  • Solo HTTPS, su un nome host pubblico (nessun IP letterale, localhost o reti private).
  • Il segreto può essere sostituito nella pagina Webhooks in qualsiasi momento; le chiamate firmate dopo utilizzano il nuovo segreto.
  • Rimuovere l'endpoint ferma nuovi eventi; i messaggi già completati mantengono il loro stato per il polling.
Webhook di consegna con firme | Documentazione BulkSMSAPI