Webhooks API
Receive real-time notifications when email events occur. Configure webhook endpoints to track email delivery, opens, clicks, bounces, and more.
Authentication
All webhook endpoints require session authentication (dashboard API).
Webhook Events
Subscribe to any combination of these events:
| Event | Description |
|---|---|
email.queued | Email has been added to the send queue |
email.sent | Email was sent to the email provider |
email.delivered | Email was delivered to the recipient’s mailbox |
email.opened | Recipient opened the email |
email.clicked | Recipient clicked a link in the email |
email.bounced | Email bounced (invalid address or mailbox full) |
email.failed | Email failed to send |
email.spam | Email was marked as spam by the recipient |
Webhook Payload
When an event occurs, Canary sends a POST request to your webhook URL with the following payload:
{
"event": "email.delivered",
"timestamp": "2024-01-20T15:30:00.000Z",
"data": {
"emailId": "eml_abc123",
"templateId": "tpl_xyz789",
"to": ["user@example.com"],
"subject": "Welcome to Our Service",
"status": "delivered"
}
}
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | The event type that triggered the webhook |
timestamp | string | ISO 8601 timestamp of when the event occurred |
data.emailId | string | Unique identifier for the email |
data.templateId | string | Template ID used (if applicable) |
data.to | string[] | Recipient email addresses |
data.subject | string | Email subject line |
data.status | string | Current email status |
data.error | string | Error message (only for failed events) |
Example Payloads
Email Sent
{
"event": "email.sent",
"timestamp": "2024-01-20T15:30:00.000Z",
"data": {
"emailId": "eml_abc123",
"templateId": "tpl_welcome",
"to": ["user@example.com"],
"subject": "Welcome!",
"status": "sent"
}
}
Email Failed
{
"event": "email.failed",
"timestamp": "2024-01-20T15:30:05.000Z",
"data": {
"emailId": "eml_abc123",
"templateId": "tpl_welcome",
"to": ["invalid@nonexistent.com"],
"subject": "Welcome!",
"status": "failed",
"error": "Recipient address rejected: User unknown"
}
}
Email Bounced
{
"event": "email.bounced",
"timestamp": "2024-01-20T15:35:00.000Z",
"data": {
"emailId": "eml_abc123",
"templateId": "tpl_welcome",
"to": ["bounced@example.com"],
"subject": "Welcome!",
"status": "bounced",
"error": "Mailbox not found"
}
}
List Webhooks
Get all webhooks for the current team.
GET /api/webhooks
Required Permission
webhooks:create
Example Request
curl "https://your-domain.com/api/webhooks" \
-H "Cookie: session=..."
Response
{
"success": true,
"data": [
{
"id": "whk_abc123",
"name": "Production Webhook",
"url": "https://api.example.com/webhooks/canary",
"events": ["email.sent", "email.delivered", "email.bounced", "email.failed"],
"isActive": true,
"lastTriggeredAt": "2024-01-20T15:30:00.000Z",
"lastSuccessAt": "2024-01-20T15:30:00.000Z",
"consecutiveFailures": 0,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T12:00:00.000Z"
}
]
}
Get Webhook
Get a single webhook by ID.
GET /api/webhooks/:id
Required Permission
webhooks:create
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Example Request
curl "https://your-domain.com/api/webhooks/whk_abc123" \
-H "Cookie: session=..."
Response
{
"success": true,
"data": {
"id": "whk_abc123",
"name": "Production Webhook",
"url": "https://api.example.com/webhooks/canary",
"events": ["email.sent", "email.delivered", "email.bounced", "email.failed"],
"isActive": true,
"lastTriggeredAt": "2024-01-20T15:30:00.000Z",
"lastSuccessAt": "2024-01-20T15:30:00.000Z",
"consecutiveFailures": 0,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T12:00:00.000Z"
}
}
Create Webhook
Create a new webhook endpoint.
POST /api/webhooks
Required Permission
webhooks:create
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Webhook name (1-100 chars) |
url | string | Yes | Webhook endpoint URL (must be HTTPS in production) |
events | string[] | Yes | Array of events to subscribe to |
Example Request
curl -X POST "https://your-domain.com/api/webhooks" \
-H "Cookie: session=..." \
-H "Content-Type: application/json" \
-d '{
"name": "Production Webhook",
"url": "https://api.example.com/webhooks/canary",
"events": ["email.sent", "email.delivered", "email.bounced", "email.failed"]
}'
Response
{
"success": true,
"data": {
"id": "whk_new123",
"name": "Production Webhook",
"url": "https://api.example.com/webhooks/canary",
"events": ["email.sent", "email.delivered", "email.bounced", "email.failed"],
"isActive": true,
"consecutiveFailures": 0,
"createdAt": "2024-01-20T10:00:00.000Z"
}
}
Update Webhook
Update an existing webhook.
PUT /api/webhooks/:id
Required Permission
webhooks:update
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Request Body
All fields are optional. Only provided fields will be updated.
| Field | Type | Description |
|---|---|---|
name | string | Webhook name (1-100 chars) |
url | string | Webhook endpoint URL |
events | string[] | Array of events to subscribe to |
isActive | boolean | Enable/disable webhook |
Example Request
curl -X PUT "https://your-domain.com/api/webhooks/whk_abc123" \
-H "Cookie: session=..." \
-H "Content-Type: application/json" \
-d '{
"events": ["email.sent", "email.delivered", "email.opened", "email.clicked", "email.bounced", "email.failed"],
"isActive": true
}'
Response
{
"success": true,
"data": {
"id": "whk_abc123",
"name": "Production Webhook",
"url": "https://api.example.com/webhooks/canary",
"events": [
"email.sent",
"email.delivered",
"email.opened",
"email.clicked",
"email.bounced",
"email.failed"
],
"isActive": true,
"updatedAt": "2024-01-20T15:00:00.000Z"
}
}
Delete Webhook
Delete a webhook.
DELETE /api/webhooks/:id
Required Permission
webhooks:delete
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Example Request
curl -X DELETE "https://your-domain.com/api/webhooks/whk_abc123" \
-H "Cookie: session=..."
Response
{
"success": true
}
List Webhook Deliveries
Get the delivery history for a webhook.
GET /api/webhooks/:id/deliveries
Required Permission
webhooks:create
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Example Request
curl "https://your-domain.com/api/webhooks/whk_abc123/deliveries" \
-H "Cookie: session=..."
Response
{
"success": true,
"data": [
{
"id": "del_xyz789",
"webhookId": "whk_abc123",
"event": "email.delivered",
"payload": {
"event": "email.delivered",
"timestamp": "2024-01-20T15:30:00.000Z",
"data": {
"emailId": "eml_abc123",
"to": ["user@example.com"],
"subject": "Welcome!",
"status": "delivered"
}
},
"responseStatus": 200,
"responseBody": "{\"received\": true}",
"success": true,
"attemptCount": 1,
"createdAt": "2024-01-20T15:30:01.000Z"
},
{
"id": "del_xyz788",
"webhookId": "whk_abc123",
"event": "email.sent",
"payload": { ... },
"responseStatus": 500,
"responseBody": "Internal Server Error",
"success": false,
"attemptCount": 3,
"createdAt": "2024-01-20T15:29:55.000Z"
}
]
}
Delivery Fields
| Field | Type | Description |
|---|---|---|
id | string | Delivery record ID |
webhookId | string | Associated webhook ID |
event | string | Event type |
payload | object | Full payload that was sent |
responseStatus | number | HTTP response status code |
responseBody | string | HTTP response body (truncated) |
success | boolean | Whether delivery was successful |
attemptCount | number | Number of delivery attempts |
createdAt | string | When the delivery was attempted |
Receiving Webhooks
Endpoint Requirements
Your webhook endpoint should:
- Accept POST requests with JSON body
- Return a 2xx status code to acknowledge receipt
- Respond within 30 seconds
- Be idempotent (handle duplicate deliveries gracefully)
Example Endpoint (Node.js/Express)
app.post('/webhooks/canary', express.json(), (req, res) => {
const { event, timestamp, data } = req.body;
console.log(`Received ${event} for email ${data.emailId}`);
switch (event) {
case 'email.delivered':
// Update your database, notify user, etc.
break;
case 'email.bounced':
// Mark email as invalid, notify admin, etc.
break;
case 'email.failed':
// Log error, trigger retry logic, etc.
break;
}
res.status(200).json({ received: true });
});
Example Endpoint (Python/Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/canary', methods=['POST'])
def handle_webhook():
payload = request.get_json()
event = payload.get('event')
data = payload.get('data')
print(f"Received {event} for email {data.get('emailId')}")
if event == 'email.delivered':
# Handle delivered event
pass
elif event == 'email.bounced':
# Handle bounced event
pass
elif event == 'email.failed':
# Handle failed event
pass
return jsonify({'received': True}), 200
Best Practices
- Use HTTPS - Always use HTTPS endpoints in production to protect webhook data
- Respond quickly - Return a 2xx response as soon as possible; process asynchronously if needed
- Handle duplicates - Webhooks may be delivered multiple times; use emailId for deduplication
- Monitor failures - Check the
consecutiveFailurescount and delivery history regularly - Log payloads - Store webhook payloads for debugging and audit purposes