Webhook Endpoints API
The Webhook Endpoints API provides full CRUD (Create, Read, Update, Delete) operations for managing webhook endpoint configurations. You can create, list, view, update, delete webhook endpoints, send test webhooks, and view delivery history programmatically.
Webhook endpoints allow you to receive real-time notifications about events in your Broadcast channel, such as email deliveries, subscriber changes, and broadcast status updates.
Required Permissions
All endpoints require authentication via an API token with appropriate permissions:
- Read Permission: Required for GET endpoints (list, show, deliveries)
- Write Permission: Required for POST, PATCH, DELETE endpoints (create, update, delete, test)
Webhook Endpoint Object
The webhook endpoint object contains the following fields:
| Field | Description |
|---|---|
| `id` | Unique identifier of the webhook endpoint |
| `url` | The HTTPS URL where webhooks will be sent |
| `description` | Optional description of the webhook endpoint |
| `active` | Whether the webhook endpoint is active |
| `event_types` | Array of event types this endpoint subscribes to |
| `retries_to_attempt` | Number of retry attempts for failed deliveries (default: 6) |
| `secret` | HMAC secret for signature verification (redacted after creation) |
| `last_response_code` | HTTP status code from the last webhook delivery |
| `last_sent_at` | Timestamp of the last webhook delivery attempt |
| `created_at` | When the webhook endpoint was created |
| `updated_at` | When the webhook endpoint was last updated |
Security Note
The webhook secret is used to sign webhook payloads using HMAC-SHA256. For security:
- The secret is only shown in full when creating a new webhook endpoint
- In subsequent GET requests, the secret is redacted (e.g.,
abcd••••••••wxyz) - Store the secret securely when you receive it during creation
- The secret cannot be modified; create a new webhook endpoint if you need a new secret
Available Event Types
You can subscribe to any combination of the following event types:
Email Events
| Event Type | Description |
|---|---|
email.sent |
Email was successfully sent to the mail server |
email.delivered |
Email was delivered to the recipient’s inbox |
email.delivery_delayed |
Email delivery is delayed |
email.opened |
Recipient opened the email |
email.clicked |
Recipient clicked a link in the email |
email.bounced |
Email bounced (hard or soft bounce) |
email.complained |
Recipient marked the email as spam |
email.failed |
Email failed to send |
Subscriber Events
| Event Type | Description |
|---|---|
subscriber.created |
New subscriber was added |
subscriber.updated |
Subscriber data was updated |
subscriber.deleted |
Subscriber was deleted |
subscriber.subscribed |
Subscriber opted in |
subscriber.unsubscribed |
Subscriber opted out |
subscriber.bounced |
Subscriber marked as bounced |
subscriber.complained |
Subscriber marked as complained |
Broadcast Events
| Event Type | Description |
|---|---|
broadcast.scheduled |
Broadcast was scheduled for sending |
broadcast.queueing |
Broadcast is being queued for sending |
broadcast.sending |
Broadcast is actively sending |
broadcast.sent |
Broadcast completed sending |
broadcast.failed |
Broadcast failed to send |
broadcast.partial_failure |
Some emails failed during broadcast |
broadcast.aborted |
Broadcast was aborted |
broadcast.paused |
Broadcast was paused |
Sequence Events
| Event Type | Description |
|---|---|
sequence.subscriber_added |
Subscriber was added to a sequence |
sequence.subscriber_completed |
Subscriber completed a sequence |
sequence.subscriber_moved |
Subscriber was moved in a sequence |
sequence.subscriber_removed |
Subscriber was removed from a sequence |
sequence.subscriber_paused |
Subscriber was paused in a sequence |
sequence.subscriber_resumed |
Subscriber was resumed in a sequence |
sequence.subscriber_error |
Error occurred with subscriber in sequence |
Other Events
| Event Type | Description |
|---|---|
message.attempt.exhausted |
All retry attempts for a webhook delivery have been exhausted |
test.webhook |
Test webhook event |
List Webhook Endpoints
GET /api/v1/webhook_endpoints
Retrieve a list of all webhook endpoints for the authenticated broadcast channel.
Query Parameters
limit(optional): Maximum number of endpoints to returnoffset(optional): Number of endpoints to skip for pagination
Request
curl -X GET \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ https://your-domain.com/api/v1/webhook_endpoints
Response
{ "data": [ { "id": 1, "url": "https://api.example.com/webhooks", "description": "Production webhook endpoint", "active": true, "event_types": ["email.sent", "email.delivered", "email.bounced"], "retries_to_attempt": 6, "secret": "abcd••••••••••••••••••••••••wxyz", "last_response_code": 200, "last_sent_at": "2024-01-15T10:30:00Z", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-15T10:30:00Z" } ], "total": 1 }
Get Webhook Endpoint
GET /api/v1/webhook_endpoints/:id
Retrieve details of a specific webhook endpoint.
Request
curl -X GET \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ https://your-domain.com/api/v1/webhook_endpoints/1
Response
{ "id": 1, "url": "https://api.example.com/webhooks", "description": "Production webhook endpoint", "active": true, "event_types": ["email.sent", "email.delivered", "email.bounced"], "retries_to_attempt": 6, "secret": "abcd••••••••••••••••••••••••wxyz", "last_response_code": 200, "last_sent_at": "2024-01-15T10:30:00Z", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-15T10:30:00Z" }
Create Webhook Endpoint
POST /api/v1/webhook_endpoints
Create a new webhook endpoint.
Required Parameters
url(required): A valid HTTPS URL where webhooks will be sentevent_types(required): Array of event types to subscribe to (at least one)
Optional Parameters
description: Description of the webhook endpointactive: Whether the endpoint is active (default: true)retries_to_attempt: Number of retry attempts (default: 6)
Request
curl -X POST \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "webhook_endpoint": { "url": "https://api.example.com/webhooks", "description": "Production webhook endpoint", "active": true, "event_types": ["email.sent", "email.delivered", "email.bounced", "subscriber.created"], "retries_to_attempt": 6 } }' \ https://your-domain.com/api/v1/webhook_endpoints
Response
Note: The secret is shown in full only on creation. Store it securely!
{ "id": 1, "url": "https://api.example.com/webhooks", "description": "Production webhook endpoint", "active": true, "event_types": ["email.sent", "email.delivered", "email.bounced", "subscriber.created"], "retries_to_attempt": 6, "secret": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6", "last_response_code": null, "last_sent_at": null, "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-01T12:00:00Z" }
Update Webhook Endpoint
PATCH /api/v1/webhook_endpoints/:id
Update an existing webhook endpoint.
Request
curl -X PATCH \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "webhook_endpoint": { "description": "Updated description", "active": false, "event_types": ["email.sent", "email.delivered"] } }' \ https://your-domain.com/api/v1/webhook_endpoints/1
Response
{ "id": 1, "url": "https://api.example.com/webhooks", "description": "Updated description", "active": false, "event_types": ["email.sent", "email.delivered"], "retries_to_attempt": 6, "secret": "a1b2••••••••••••••••••••••••y5z6", "last_response_code": 200, "last_sent_at": "2024-01-15T10:30:00Z", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-15T11:00:00Z" }
Delete Webhook Endpoint
DELETE /api/v1/webhook_endpoints/:id
Delete a webhook endpoint.
Request
curl -X DELETE \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ https://your-domain.com/api/v1/webhook_endpoints/1
Response
{ "message": "Webhook endpoint deleted successfully" }
Send Test Webhook
POST /api/v1/webhook_endpoints/:id/test
Send a test webhook to verify your endpoint is working correctly. The test webhook will contain realistic sample data for the specified event type.
Query Parameters
event_type(optional): The event type to test (default: “test.webhook”). Must be one of the event types configured for this endpoint.
Request
curl -X POST \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_type": "email.delivered" }' \ https://your-domain.com/api/v1/webhook_endpoints/1/test
Response
{ "message": "Test webhook queued for delivery", "event_type": "email.delivered", "delivery_id": 42 }
List Webhook Deliveries
GET /api/v1/webhook_endpoints/:id/deliveries
Retrieve the delivery history for a webhook endpoint, including successful deliveries and failed attempts.
Query Parameters
limit(optional): Maximum number of deliveries to returnoffset(optional): Number of deliveries to skip for pagination
Request
curl -X GET \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ https://your-domain.com/api/v1/webhook_endpoints/1/deliveries
Response
{ "data": [ { "id": 42, "event_type": "email.delivered", "event_data": { "receipt_id": 1234, "email": "[email protected]", "delivered": true, "opened": false }, "attempt_number": 1, "response_status": 200, "response_body": "OK", "error_message": null, "first_attempt_at": "2024-01-15T10:30:00Z", "last_attempt_at": "2024-01-15T10:30:00Z", "next_retry_at": null, "successfully_delivered_at": "2024-01-15T10:30:00Z", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ], "total": 1 }
Webhook Delivery Object
The delivery object contains information about each webhook delivery attempt:
| Field | Description |
|---|---|
| `id` | Unique identifier of the delivery |
| `event_type` | The type of event that triggered this delivery |
| `event_data` | The payload that was sent to the webhook URL |
| `attempt_number` | Current attempt number (1-6) |
| `response_status` | HTTP status code from your endpoint |
| `response_body` | Response body from your endpoint |
| `error_message` | Error message if delivery failed |
| `first_attempt_at` | When the first delivery attempt was made |
| `last_attempt_at` | When the most recent attempt was made |
| `next_retry_at` | When the next retry will be attempted (if applicable) |
| `successfully_delivered_at` | When delivery was successful (null if not yet delivered) |
Retry Schedule
Failed webhook deliveries are automatically retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 5 hours |
After 6 failed attempts, the delivery is marked as failed and a message.attempt.exhausted event is triggered.
Verifying Webhook Signatures
All webhooks are signed using HMAC-SHA256. To verify a webhook:
- Extract the signature from the
broadcast-webhook-signatureheader - Extract the timestamp from the
broadcast-webhook-timestampheader - Create the signed payload:
{timestamp}.{raw_request_body} - Calculate HMAC-SHA256 using your secret and the signed payload
- Compare the calculated signature with the one in the header
Header Format
broadcast-webhook-signature: v1,{base64_encoded_signature}
broadcast-webhook-timestamp: 1705312200
Example Verification (Ruby)
def verify_webhook(payload, headers, secret) signature_header = headers['broadcast-webhook-signature'] timestamp = headers['broadcast-webhook-timestamp'] version, signature = signature_header.split(',') return false unless version == 'v1' # Check timestamp (5 minute window) return false if (Time.now.to_i - timestamp.to_i).abs > 300 signed_payload = "#{timestamp}.#{payload}" expected = Base64.strict_encode64( OpenSSL::HMAC.digest('sha256', secret, signed_payload) ) ActiveSupport::SecurityUtils.secure_compare(signature, expected) end
Error Responses
401 Unauthorized
{ "error": "Unauthorized" }
Returned when the API token is missing, invalid, or lacks the required permissions.
404 Not Found
{ "error": "Webhook endpoint not found" }
Returned when the specified webhook endpoint doesn’t exist or belongs to a different channel.
422 Unprocessable Entity
{ "error": "Url is invalid, Event types must have one selected" }
Common validation errors: - Missing or invalid URL (must be HTTP or HTTPS) - No event types selected - Duplicate URL (each URL must be unique per channel) - Invalid event type for test webhook
Example: Complete Setup Flow
# 1. Create a webhook endpoint curl -X POST \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "webhook_endpoint": { "url": "https://api.myapp.com/broadcast-webhooks", "description": "Email event notifications", "event_types": ["email.sent", "email.delivered", "email.bounced", "email.opened"] } }' \ https://your-domain.com/api/v1/webhook_endpoints # Response: Save the secret! It won't be shown again. # { "id": 1, "secret": "abc123...", ... } # 2. Send a test webhook to verify your endpoint curl -X POST \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"event_type": "email.delivered"}' \ https://your-domain.com/api/v1/webhook_endpoints/1/test # 3. Check the delivery history curl -X GET \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ https://your-domain.com/api/v1/webhook_endpoints/1/deliveries # 4. Verify successful delivery # Response shows response_status: 200 and successfully_delivered_at is set