Webhooks
Configure project webhooks and inbound receiving routes, signatures, tests, and delivery history.
This reference is generated from the API’s routes and request validators. Download the OpenAPI specification. JSON bodies go directly to the API; MCP tools use a body object and a query object.
Generated request examples contain placeholders. Replace addresses, URLs, resource IDs and other placeholder values before sending. Conditional rules and refinements are enforced by the API at runtime.
/webhookslist (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: list_webhooks
Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhookscreate (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: create_webhook
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
url | Yes | string |
description | No / conditional | string |
events | No / conditional | array |
inboundDomain | No / conditional | string, nullable |
inboundPattern | No / conditional | string, nullable |
enabled | No / conditional | boolean |
Complete request JSON Schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"maxLength": 500
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"email.sent",
"email.opened",
"email.clicked",
"email.bounced",
"email.complained",
"email.unsubscribed",
"email.received",
"contact.created",
"contact.updated",
"contact.subscribed",
"contact.unsubscribed",
"contact.deleted",
"campaign.scheduled",
"campaign.sent",
"campaign.cancelled"
]
}
},
"inboundDomain": {
"type": "string",
"maxLength": 253,
"nullable": true
},
"inboundPattern": {
"type": "string",
"maxLength": 64,
"nullable": true
},
"enabled": {
"type": "boolean"
}
},
"required": [
"url"
],
"additionalProperties": false
}Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 201, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhooks/:idget (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: get_webhook
| Parameter | Location | Required | Type |
|---|---|---|---|
id | path | Yes | string |
Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhooks/:idupdate (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: update_webhook
| Parameter | Location | Required | Type |
|---|---|---|---|
id | path | Yes | string |
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
url | No / conditional | string |
description | No / conditional | string |
events | No / conditional | array |
inboundDomain | No / conditional | string, nullable |
inboundPattern | No / conditional | string, nullable |
enabled | No / conditional | boolean |
rotateSecret | No / conditional | boolean |
Complete request JSON Schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"maxLength": 500
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"email.sent",
"email.opened",
"email.clicked",
"email.bounced",
"email.complained",
"email.unsubscribed",
"email.received",
"contact.created",
"contact.updated",
"contact.subscribed",
"contact.unsubscribed",
"contact.deleted",
"campaign.scheduled",
"campaign.sent",
"campaign.cancelled"
]
}
},
"inboundDomain": {
"type": "string",
"maxLength": 253,
"nullable": true
},
"inboundPattern": {
"type": "string",
"maxLength": 64,
"nullable": true
},
"enabled": {
"type": "boolean"
},
"rotateSecret": {
"type": "boolean"
}
},
"additionalProperties": false
}Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhooks/:iddelete (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: delete_webhook
| Parameter | Location | Required | Type |
|---|---|---|---|
id | path | Yes | string |
Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhooks/:id/deliveriesdeliveries (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: list_webhook_deliveries
| Parameter | Location | Required | Type |
|---|---|---|---|
id | path | Yes | string |
Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/webhooks/:id/testtest (webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: test_webhook
| Parameter | Location | Required | Type |
|---|---|---|---|
id | path | Yes | string |
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
event | No / conditional | string (test, email.received) |
recipient | No / conditional | string, email |
Complete request JSON Schema
{
"type": "object",
"properties": {
"event": {
"type": "string",
"enum": [
"test",
"email.received"
]
},
"recipient": {
"type": "string",
"maxLength": 320,
"format": "email"
}
},
"additionalProperties": false
}Project-scoped customer operation. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhookslist (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhookscreate (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
url | Yes | string |
description | No / conditional | string |
events | No / conditional | array |
inboundDomain | No / conditional | string, nullable |
inboundPattern | No / conditional | string, nullable |
enabled | No / conditional | boolean |
Complete request JSON Schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"maxLength": 500
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"email.sent",
"email.opened",
"email.clicked",
"email.bounced",
"email.complained",
"email.unsubscribed",
"email.received",
"contact.created",
"contact.updated",
"contact.subscribed",
"contact.unsubscribed",
"contact.deleted",
"campaign.scheduled",
"campaign.sent",
"campaign.cancelled"
]
}
},
"inboundDomain": {
"type": "string",
"maxLength": 253,
"nullable": true
},
"inboundPattern": {
"type": "string",
"maxLength": 64,
"nullable": true
},
"enabled": {
"type": "boolean"
}
},
"required": [
"url"
],
"additionalProperties": false
}Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 201, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhooks/:idget (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
id | path | Yes | string |
Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhooks/:idupdate (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
id | path | Yes | string |
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
url | No / conditional | string |
description | No / conditional | string |
events | No / conditional | array |
inboundDomain | No / conditional | string, nullable |
inboundPattern | No / conditional | string, nullable |
enabled | No / conditional | boolean |
rotateSecret | No / conditional | boolean |
Complete request JSON Schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"maxLength": 500
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"email.sent",
"email.opened",
"email.clicked",
"email.bounced",
"email.complained",
"email.unsubscribed",
"email.received",
"contact.created",
"contact.updated",
"contact.subscribed",
"contact.unsubscribed",
"contact.deleted",
"campaign.scheduled",
"campaign.sent",
"campaign.cancelled"
]
}
},
"inboundDomain": {
"type": "string",
"maxLength": 253,
"nullable": true
},
"inboundPattern": {
"type": "string",
"maxLength": 64,
"nullable": true
},
"enabled": {
"type": "boolean"
},
"rotateSecret": {
"type": "boolean"
}
},
"additionalProperties": false
}Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhooks/:iddelete (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
id | path | Yes | string |
Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhooks/:id/deliveriesdeliveries (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
id | path | Yes | string |
Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
/projects/:projectId/webhooks/:id/testtest (projects/:projectId/webhooks)
Authentication: Secret project API key. Resources are scoped to that project.
MCP: Not exposed through remote MCP; account administration, credentials, browser tracking, and dashboard aliases retain their own authorization.
| Parameter | Location | Required | Type |
|---|---|---|---|
projectId | path | Yes | string |
id | path | Yes | string |
Request body: application/json
| Field | Required | Type / constraints |
|---|---|---|
event | No / conditional | string (test, email.received) |
recipient | No / conditional | string, email |
Complete request JSON Schema
{
"type": "object",
"properties": {
"event": {
"type": "string",
"enum": [
"test",
"email.received"
]
},
"recipient": {
"type": "string",
"maxLength": 320,
"format": "email"
}
},
"additionalProperties": false
}Dashboard alias; MCP uses canonical /webhooks. Server enforces project ownership and additional cross-field validation. IDs are UUIDs, not prefixed placeholders.
Response codes: 200, 400, 401, 403, 404, 429, 500, 503. Errors do not imply that an uncertain send is safe to repeat. A successful sending response confirms API acceptance, not inbox delivery.
Webhooks deliver real-time notifications to your server when events occur. Sendbound sends an HTTP POST to your endpoint for each matching event.
Webhook subscriptions are scoped to the project authenticated by your secret API
key. Use /webhooks for API integrations. The dashboard also uses the equivalent
/projects/:projectId/webhooks endpoints with its authenticated project session.
For MX configuration, catch-all receiving routes, bodies, attachment downloads, V2 signature verification, retry timing, and realistic tests, see the Inbound email guide in the Concepts section.
Webhook object
{
"id": "c856112f-3a02-5402-a6f5-a702a637dc01",
"url": "https://yourapp.com/hooks/sendbound",
"description": "Production delivery events",
"events": ["email.received"],
"inboundDomain": "inbound.example.com",
"inboundPattern": "*",
"enabled": true,
"createdAt": "2026-01-20T09:00:00Z"
}
Event types
| Event | Trigger |
|---|---|
email.sent | Email sent through the configured transport; not proof of inbox placement |
email.received | Email received and parsed for this project |
email.opened | Recipient opened the email |
email.clicked | Recipient clicked a tracked link |
email.bounced | Permanent delivery failure |
email.complained | Recipient marked the email as spam |
contact.created | New contact added |
contact.updated | Contact updated |
contact.subscribed | Contact subscribed |
contact.unsubscribed | Contact unsubscribed |
contact.deleted | Contact deleted |
email.unsubscribed | Email unsubscribe event |
campaign.scheduled | Campaign scheduled |
campaign.sent | Campaign sent |
campaign.cancelled | Campaign cancelled |
Webhook payload
Bodies are event-specific JSON objects, not a standard
{event, timestamp, data} envelope. Read the event name from Sendbound-Event.
Verify the raw body, timestamp, and delivery ID with Sendbound-Signature-V2
before processing. An inbound example:
{
"id": "c856112f-3a02-5402-a6f5-a702a637dc01",
"from": "sender@example.net",
"to": "jane@inbound.example.com",
"recipients": ["jane@inbound.example.com"],
"subject": "Example email",
"text": "Hello",
"html": "<p>Hello</p>",
"messageId": "<original@example.net>",
"receivedAt": "2026-10-16T12:00:00.000Z",
"attachments": []
}
List webhooks
/webhooksReturns all webhook subscriptions for the project.
Create a webhook
Get a webhook
/webhooks/:idReturns a single webhook subscription by ID.
Update a webhook
Delete a webhook
/webhooks/:idRemoves the webhook subscription and delivery history. Returns 200 with success: true. Does not alter your DNS.
Test a webhook
For inbound tests, optional recipient must match the saved receiving domain and
address pattern. Use an address your application recognizes. Omitted recipients
default to the saved exact address, or test@DOMAIN for a catch-all. Tests are
synthetic (test: true), one-shot and do not confirm DNS or SMTP. Failed delivery
returns HTTP 502; the error reports the receiving endpoint's HTTP status, and
delivery history includes its response body. Verify signatures before
acknowledging synthetic tests; never bypass authentication.
Verifying signatures
New integrations should verify Sendbound-Signature-V2. Its value is
v2=<hex HMAC-SHA256>, using the signing-secret string and the exact input
timestamp + "." + deliveryId + "." + rawBody. The timestamp is Unix
milliseconds. Check timestamp freshness and deduplicate by
Sendbound-Delivery-Id; retries retain that ID but have fresh timestamps.
import crypto from 'crypto';
function verifyWebhook(rawBody, signatureHeader, secret, timestamp, deliveryId) {
if (!/^\d+$/.test(timestamp) || !deliveryId ||
Math.abs(Date.now() - Number(timestamp)) > 300000 ||
!/^v2=[0-9a-f]{64}$/.test(signatureHeader)) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${deliveryId}.`).update(rawBody).digest();
const supplied = Buffer.from(signatureHeader.slice(3), 'hex');
return expected.length === supplied.length && crypto.timingSafeEqual(expected, supplied);
}
The signing secret is returned once when you create the webhook. If you lose it, rotate it with PATCH using rotateSecret: true.
For compatibility, Sendbound-Signature: sha256=<hex> still signs only the raw
body. It does not authenticate the timestamp or delivery ID.
Delivery history and retries
GET /webhooks/:id/deliveries returns recent status, attempts, HTTP response
code, and response text. Use ?before=<ISO timestamp> for earlier deliveries.
The webhook detail endpoint also includes recent deliveries.
Production jobs make eight total attempts, with exponential waits of 30, 60, 120, 240, 480, 960, and 1920 seconds. Each HTTP attempt times out after ten seconds. Network failures, timeouts, redirects, and non-2xx responses fail the attempt. Tests are one-shot and are not retried.