Webhooks
Signature header format: t=timestamp,v1=hex where hex is HMAC-SHA256 of `${timestamp}.${rawBody}`. Reject if |now − t| > 300s (configurable).
verify (node)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(header, rawBody, secret, now = Math.floor(Date.now() / 1000)) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=").map((s) => s.trim())));
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(now - timestamp) > 300) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const left = Buffer.from(expected);
const right = Buffer.from(parts.v1 ?? "");
return left.length === right.length && timingSafeEqual(left, right);
}Delivery is at-least-once. Dedupe on the event id in the payload. Retry offsets: 0, 1m, 5m, 15m, 1h, 6h, 24h.
- message.created
- message.queued
- message.submitted
- message.delivered
- message.failed
- message.expired
- message.rejected
- campaign.started
- campaign.completed
- campaign.failed
- wallet.low_balance
- sender_id.approved
- sender_id.rejected