Reference
Webhooks
Delivery is asynchronous. Webhooks tell your application what happened to each message without polling.
Setting up an endpoint
Add an HTTPS URL under Webhooks in the dashboard and choose the event types you want. Each endpoint gets its own signing secret, shown once. You can send a test event from the same screen and replay any event from the last 30 days.
Event types
| Field | Type | Description |
|---|---|---|
email.sent | event | Accepted by Amazon SES. |
email.delivered | event | The receiving mail server accepted the message. |
email.bounced | event | Hard or soft bounce. Hard bounces are added to your suppression list automatically. |
email.complained | event | The recipient marked the message as spam. The address is suppressed and counts toward your complaint rate. |
email.failed | event | Permanently failed after retries. |
sms.sent | event | Accepted by the mobile network. |
sms.delivered | event | Delivery receipt received from the handset. |
sms.failed | event | Rejected by the network or expired before delivery. Credits are refunded when the message never left our platform. |
sms.opted_out | event | The recipient replied STOP. The number is suppressed for marketing messages from your organisation. |
Payload
POST /webhooks/sendbaze HTTP/1.1
Content-Type: application/json
Sendbaze-Signature: t=1758528842,v1=6f1c0a9e2d...
Sendbaze-Event-Id: evt_01j9x7m3p5q7r9s1t3u5v7w9x1
{
"id": "evt_01j9x7m3p5q7r9s1t3u5v7w9x1",
"type": "email.bounced",
"occurredAt": "2026-09-22T08:14:04.201Z",
"data": {
"id": "em_01j9x7k2r4m8q3v5n6b7c8d9e0",
"channel": "email",
"status": "bounced",
"to": "thandi@example.com",
"from": "orders@yourshop.co.za",
"tags": ["order-shipped"],
"error": { "code": "hard_bounce", "message": "550 5.1.1 The email account does not exist." }
}
}data is the same message object that GET /v1/emails/:id or GET /v1/sms/:id returns, minus the events array.
Verifying the signature
Every request carries a Sendbaze-Signature header with a timestamp and an HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with your endpoint secret. Verify it against the raw request body before you parse the JSON, and reject timestamps older than five minutes to defeat replays.
verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySendbazeSignature(
rawBody: string,
header: string,
secret: string,
toleranceSeconds = 300,
): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const given = String(parts.v1 ?? "");
return given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Retries and ordering
- Respond with any
2xxwithin 10 seconds. Anything else is retried with backoff: 1 minute, 5 minutes, 30 minutes, 2 hours, then 24 hours. - Events can arrive out of order or more than once. Use
Sendbaze-Event-Idto de-duplicate andoccurredAtto order. - An endpoint that fails for 3 days is disabled and the organisation owner is emailed.
Bounce and complaint events are the ones that protect your sender reputation. Handle them by updating your own records so you stop sending to those addresses from other systems too.