Webhooks
| Method | Path | Scope | SDK |
|---|---|---|---|
GET | /api/v1/webhooks | any valid key | webhooks.list |
POST | /api/v1/webhooks | webhooks:write | webhooks.create |
PATCH | /api/v1/webhooks/:id | webhooks:write | webhooks.update |
DELETE | /api/v1/webhooks/:id | webhooks:write | webhooks.delete |
GET | /api/v1/webhooks/:id/deliveries | any valid key | webhooks.deliveries |
POST | /api/v1/webhooks/:id/rotate | webhooks:write | webhooks.rotate |
POST | /api/v1/webhooks/:id/ping | webhooks:write | webhooks.ping |
For verifying what Mailroom sends to you, see the webhooks guide.
POST /api/v1/webhooks
| Field | Type | Notes |
|---|---|---|
url | string | Required. Public HTTPS URL. Validated against SSRF — private, loopback, and link-local addresses are refused, and redirects are never followed. |
events | string[] | Subscribe to a subset. Omit or empty = every event. Must be known event types. |
secret | string | Bring your own signing secret; otherwise one is generated. |
201
{
"ok": true,
"endpoint": {
"id": "wh_7a2c…",
"url": "https://app.example.com/hooks/mailroom",
"events": ["email.bounced", "contact.unsubscribed"],
"enabled": true,
"created_at": "2026-07-28T10:00:00.000Z"
},
"secret": "whsec_9f2a…"
}
secret appears only in this response (and in rotate). It cannot be read back —
store it before you discard the response.
Subscribable event types
contact.subscribed, contact.unsubscribed, email.sent, email.delivered,
email.opened, email.clicked, email.bounced, email.complained,
sequence.completed, campaign.completed.
An unknown name in events is a 422. Note that webhook.ping is not
subscribable — see below.
GET /api/v1/webhooks
{ "ok": true, "webhooks": [{ "id": "wh_7a2c…", "url": "…", "events": null, "enabled": true, "created_at": "…" }] }
events: null means all events. Secrets are never returned.
PATCH /api/v1/webhooks/:id
Body: any of { "url", "events", "enabled" }.
{ "ok": true, "endpoint": { "id": "wh_7a2c…", "enabled": false, … } }
Disabling with enabled: false stops delivery without losing the endpoint or its
secret — the reasonable first move when your receiver is broken.
DELETE /api/v1/webhooks/:id
{ "ok": true, "id": "wh_7a2c…", "deleted": true }
Queued deliveries for the endpoint are dropped.
GET /api/v1/webhooks/:id/deliveries
Query: limit (rows, newest first).
{
"ok": true,
"deliveries": [
{
"id": "d_51b0…",
"event_type": "email.bounced",
"payload": { "email": "bounced@example.com", "template": "welcome" },
"status": "failed",
"attempts": 3,
"response_status": 500,
"next_attempt_at": "2026-07-28T10:24:00.000Z",
"created_at": "2026-07-28T10:08:00.000Z"
}
]
}
status: pending, delivered, failed. Deliveries are drained about once a
minute; a non-2xx response is retried with exponential backoff up to 6 attempts
(2, 4, 8 … minutes, capped at 60), then left failed. This log is where you look
first when your receiver "never got the event".
POST /api/v1/webhooks/:id/rotate
Mints a new signing secret and returns it once. The old secret stops working immediately, so deploy the new one before rotating (or accept a gap).
{ "ok": true, "endpoint": { "id": "wh_7a2c…", … }, "secret": "whsec_new…" }
POST /api/v1/webhooks/:id/ping
Sends a signed webhook.ping event inline and reports what your receiver said.
Not persisted to the delivery log, and webhook.ping is deliberately not a
subscribable type — so a ping never reaches production handlers that filter on
subscribed events.
{ "ok": true, "delivered": true, "responseStatus": 204 }
delivered: false with responseStatus: null means the request never completed
(DNS, TLS, timeout, or the SSRF gate refused the address).
404 not_found on any of these endpoints means no such webhook in this project.