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 URL where webhooks will be sent (`http://` or `https://`; HTTPS is recommended). Unique per channel
`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` The endpoint’s maximum retry attempts (default: 6). See the retry schedule
`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

2.53+

retries_to_attempt is the number of retries after the first attempt: 0 to 20. A failed delivery is retried that many times on the retry schedule.

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

A change made through the API is never rejected because its webhook couldn’t be sent. If a webhook had a problem, the request still succeeds and says so in a warnings array and a Broadcast-Warning header. See API Response Warnings.

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. Not sent for broadcasts while parallel sending is on
email.send_delayed A transactional email is waiting because its email server is at the hourly limit. Sent once, at the first wait
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. Not sent for broadcasts while parallel sending is on

With parallel sending on (PARALLEL_BROADCAST_SENDING=true), broadcasts send broadcast.batch_completed instead of per-recipient email.sent and email.failed events. Sequential broadcasts (the default), sequences and transactional emails still send email.sent and email.failed. Delivery, open, click, bounce and complaint events are the same in both modes.

Subscriber Events

Event Type Description
subscriber.created New subscriber was added
subscriber.updated Subscriber data was updated
subscriber.preferences_updated Topic values changed; sent together with subscriber.updated
subscriber.deleted Subscriber was deleted. Not sent for subscribers removed by a purge
subscriber.subscribed Subscriber opted in
subscriber.unsubscribed Subscriber opted out
subscriber.bounced Subscriber marked as bounced
subscriber.complained Subscriber marked as complained

Subscriber List Events

Event Type Description
subscribers.purged Every subscriber in the channel was purged. Sent once per purge
subscribers.purge_failed A purge stopped before it finished; running it again is safe

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
broadcast.batch_completed One batch run of a parallel broadcast finished or stopped early. Only sent when parallel sending is on

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 Subscriber’s enrolment stopped being able to progress; the reason is in error_message. A step failing to send is email.failed instead

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 return, from 1 to 1000
  • offset (optional): Number of endpoints to skip, 0 or more

A limit or offset outside those ranges, or that is not a whole number, returns 422 Unprocessable Entity.

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 http:// or https:// URL where webhooks will be sent (HTTPS is recommended). Each URL can be used only once per channel
  • event_types (required): Array of event types to subscribe to (at least one)

Optional Parameters

  • description: Description of the webhook endpoint
  • active: Whether the endpoint is active (default: true)
  • retries_to_attempt: Maximum retry attempts (default: 6). See the retry schedule

2.53+

retries_to_attempt must be a whole number from 0 to 20, on create and on update. Any other value returns 422 Unprocessable Entity with an error message.

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 return, from 1 to 1000
  • offset (optional): Number of deliveries to skip, 0 or more

A limit or offset outside those ranges, or that is not a whole number, returns 422 Unprocessable Entity.

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": "subscriber@example.com",
        "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` The attempt number of the delivery (see below the table)
`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)
`created_at` When the delivery record was created
`updated_at` When the delivery record was last updated

2.53+

attempt_number is the attempt due next while the delivery is pending. After that, it is the last attempt made, and never less than 1. It is the same number the dashboard shows.

Retry Schedule

2.53+

The first attempt is immediate. When it fails, Broadcast retries the delivery up to retries_to_attempt times (0 to 20, default 6). The wait before each retry is:

Retry Wait
1 5 seconds
2 5 minutes
3 30 minutes
4 2 hours
5 5 hours
6 10 hours
7 to 20 10 hours

Each wait counts from the attempt that failed.

When the last retry fails, the delivery is marked as failed and a message.attempt.exhausted event is sent. Its total_attempts is the number of attempts made. A delivery that was given up before any attempt reports 0. A failed message.attempt.exhausted delivery does not send another message.attempt.exhausted event.

If you lower retries_to_attempt, a pending delivery that is already past the new limit gets no more attempts. It is marked as failed and sends message.attempt.exhausted.

Verifying Webhook Signatures

All webhooks are signed using HMAC-SHA256. To verify a webhook:

  1. Extract the signature from the broadcast-webhook-signature header
  2. Extract the timestamp from the broadcast-webhook-timestamp header
  3. Create the signed payload: {timestamp}.{raw_request_body}
  4. Calculate HMAC-SHA256 using your secret and the signed payload
  5. 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

Last updated

Was this page helpful?

Thanks for your feedback!

Thanks for letting us know. We'll work on improving this page.