Verfolgung

Zustellungs-Webhooks mit Signaturen

Legen Sie einen HTTPS-Endpunkt auf der Webhooks-Seite der Konsole fest. Wenn eine Nachricht einen endgültigen Status erreicht, senden wir einen POST mit einem JSON-Ereignis an diesen Endpunkt.

Ereignisse

typeWann
message.deliveredDie Nachricht wurde zugestellt.
message.failedDie Nachricht ist fehlgeschlagen; data.error enthält den Code. Der Betrag wurde erstattet.
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 und Signatur

Jeder Aufruf enthält drei Header im Standard-Webhooks-Format:

HeaderWert
webhook-idDie Event-ID (evt_...). Dieselbe ID wird für jeden Wiederholungsversuch eines Events verwendet.
webhook-timestampUnix-Zeit in Sekunden, zu der dieser Versuch signiert wurde.
webhook-signaturev1, gefolgt von der base64 HMAC-SHA256-Signatur. Mehrere Signaturen können aufgelistet sein, durch Leerzeichen getrennt: akzeptiere den Aufruf, wenn eine davon übereinstimmt.

Um einen Aufruf zu überprüfen: Nehmen Sie das Geheimnis aus der Konsole (es beginnt mit whsec_), dekodieren Sie den Teil nach whsec_ mit Base64URL, um den Schlüssel zu erhalten, berechnen Sie HMAC-SHA256 von webhook-id + "." + webhook-timestamp + "." + raw body, kodieren Sie es mit Base64 und vergleichen Sie es in konstanter Zeit mit der Signatur. Lehnen Sie Zeitstempel ab, die mehr als fünf Minuten von Ihrer Uhrzeit entfernt sind. Verwenden Sie immer den Rohkörper: Ein geparster und neu serialisierter JSON-Körper wird nicht übereinstimmen.

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

Antworten und Wiederholungen

  • Antworte mit einem beliebigen 2xx-Status innerhalb von 10 Sekunden. Führe die langsame Arbeit nach der Antwort aus einer Warteschlange aus.
  • Weiterleitungen werden nicht verfolgt: Richte die Einstellung auf die endgültige Adresse aus.
  • Ein Aufruf, der ein Timeout hat oder etwas anderes als 2xx antwortet, wird nach 30 Sekunden, 2 Minuten, 10 Minuten, 30 Minuten, 1 Stunde, 2 Stunden, 4 Stunden, 8 Stunden, 8 Stunden erneut versucht: insgesamt 10 Versuche, etwa 24 Stunden nach dem ersten.
  • Jeder Versuch wird auf der Webhooks-Seite mit dem HTTP-Status und der benötigten Zeit aufgelistet.

Duplikate und Reihenfolge

Ein Event kann zweimal ankommen, zum Beispiel wenn Ihr Endpunkt es gespeichert hat, die Antwort aber verloren ging. Speichern Sie webhook-id und überspringen Sie IDs, die Sie bereits verarbeitet haben. Events für verschiedene Nachrichten können in beliebiger Reihenfolge eintreffen; lesen Sie data.status und data.done_at, anstatt sich auf die Ankunftsreihenfolge zu verlassen.

Endpunkt-Regeln

  • Nur HTTPS, auf einem öffentlichen Hostnamen (keine IP-Literale, localhost oder private Netzwerke).
  • Das Secret kann jederzeit auf der Webhooks-Seite ersetzt werden; Aufrufe, die danach signiert werden, verwenden das neue Secret.
  • Das Entfernen des Endpunkts stoppt neue Events; Nachrichten, die bereits abgeschlossen sind, behalten ihren Status für das Polling.
Zustellungs-Webhooks mit Signaturen | BulkSMSAPI Dokumentation