Webhooks
Dispatch POSTs delivery-status events to destinations you configure. You can manage destinations from the dashboard or programmatically via the /webhook-configs endpoints.
Webhook Config CRUD
All endpoints below authenticate with an API key (no specific scope required). Each user may configure up to 10 webhook destinations.
Create
POST /api/v1/webhook-configs
Authorization: Bearer {api_key}{
"name": "Prod delivery webhook",
"url": "https://yourapp.com/dispatch/webhook",
"secret": "optional-signing-secret",
"events": ["delivered", "bounced", "failed"],
"description": "Production delivery status listener"
}| Field | Required | Description |
|---|---|---|
| name | No | Human label |
| url | Yes | HTTPS endpoint Dispatch will POST to |
| secret | No | Signing secret. If omitted, Dispatch generates one and returns it. |
| events | No | Array of event types to subscribe to. Empty array or omitted = subscribe to all events. |
| description | No | Free-text description |
List
GET /api/v1/webhook-configs
Authorization: Bearer {api_key}Get
GET /api/v1/webhook-configs/{id}
Authorization: Bearer {api_key}Update
PUT /api/v1/webhook-configs/{id}
Authorization: Bearer {api_key}All fields optional — supplied fields are updated, others left unchanged.
{
"url": "https://yourapp.com/dispatch/webhook-v2",
"is_active": true,
"events": ["delivered", "bounced", "failed", "opened"]
}Delete
DELETE /api/v1/webhook-configs/{id}
Authorization: Bearer {api_key}Event payload
When Dispatch sends a webhook, the body is a JSON object:
{
"event": "delivered",
"message_id": "msg_abc123",
"recipient": "[email protected]",
"subject": "Hello World",
"status": "delivered",
"provider": "ses",
"timestamp": 1741363205
}| Field | Description |
|---|---|
| event | Event type — see Event Types |
| message_id | UUID of the Dispatch message |
| recipient | Destination address |
| subject | Email subject |
| status | Current message status |
| provider | Delivery provider that produced the event |
| timestamp | Unix epoch seconds |
| error | Present only on failure events |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Dispatch-Webhook/1.0 |
X-Webhook-Signature | hex(hmac_sha256(body, secret)) when a secret is configured |
Signature verification (Node.js example)
import crypto from 'node:crypto'
function verify(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signatureHeader, 'hex')
)
}Compute the HMAC over the exact raw request body before any JSON parsing.
Event Types
| Event | Triggered when |
|---|---|
| queued | Message accepted into the task queue |
| sending | Worker begins provider delivery |
| sent | Provider accepted the message |
| delivered | Confirmed delivered to recipient |
| bounced | Hard or soft bounce reported by the provider |
| failed | Permanent delivery failure |
| opened | Recipient opened the email (if tracking pixel was included) |
An empty events array subscribes to all event types above.
Retries
Dispatch retries failed deliveries with exponential backoff. Treat your endpoint as idempotent — the same message_id + event combination may arrive more than once.
Response expectations
Return 2xx within 10 seconds to acknowledge receipt. Any other response (or a timeout) is treated as failure and will be retried.