Webhooks
Receive real-time notifications when events happen in your newsletter.
Setting Up Webhooks
Via Dashboard
- Go to your newsletter → Settings → Webhooks
- Click "Add Webhook"
- Enter your endpoint URL
- Select events to subscribe to
- Save the webhook
Via API
POST /api/v1/webhook_subscriptions
{
"url": "https://your-app.com/webhooks/dailydraft",
"events": ["subscriber.created", "issue.sent"]
}
Available Events
| Event | Description |
|---|---|
subscriber.created |
New subscriber added |
subscriber.updated |
Subscriber details changed |
subscriber.deleted |
Subscriber removed |
issue.sent |
Issue finished sending |
issue.published |
Issue published to the web |
email.opened |
Subscriber opened email |
email.clicked |
Subscriber clicked a link |
email.bounced |
Email bounced (hard bounce) |
email.unsubscribed |
Subscriber unsubscribed via email link |
Webhook Payload
All webhooks follow this format:
{
"event": "subscriber.created",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
// Event-specific data
}
}
Event Payloads
subscriber.created
{
"event": "subscriber.created",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"subscription_id": 456,
"email": "[email protected]",
"name": "New User",
"status": "active",
"subscribed_at": "2025-01-10T12:00:00Z",
"tags": []
}
}
subscriber.updated
{
"event": "subscriber.updated",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"subscription_id": 456,
"email": "[email protected]",
"name": "Updated Name",
"status": "active",
"subscribed_at": "2025-01-09T10:00:00Z",
"tags": ["vip"]
}
}
subscriber.deleted
{
"event": "subscriber.deleted",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"subscription_id": 456,
"email": "[email protected]",
"name": "Removed User",
"status": "unsubscribed",
"subscribed_at": "2025-01-01T10:00:00Z",
"tags": []
}
}
issue.sent
{
"event": "issue.sent",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"issue_id": 42,
"subject": "Weekly Newsletter #42",
"path": "weekly-newsletter-42",
"status": "sent",
"sent_at": "2025-01-10T09:00:00Z",
"published_at": null,
"article_count": 5
}
}
issue.published
{
"event": "issue.published",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"issue_id": 42,
"subject": "Weekly Newsletter #42",
"path": "weekly-newsletter-42",
"status": "published",
"sent_at": "2025-01-10T09:00:00Z",
"published_at": "2025-01-10T12:00:00Z",
"article_count": 5
}
}
email.opened
{
"event": "email.opened",
"timestamp": "2025-01-10T12:05:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"delivery_id": 789,
"issue_id": 42,
"email": "[email protected]",
"opened_at": "2025-01-10T12:05:00Z",
"clicked_at": null
}
}
email.clicked
{
"event": "email.clicked",
"timestamp": "2025-01-10T12:10:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"delivery_id": 789,
"issue_id": 42,
"email": "[email protected]",
"opened_at": "2025-01-10T12:05:00Z",
"clicked_at": "2025-01-10T12:10:00Z",
"clicked_url": "https://example.com/article"
}
}
email.bounced
{
"event": "email.bounced",
"timestamp": "2025-01-10T12:00:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"delivery_id": 789,
"issue_id": 42,
"email": "[email protected]",
"opened_at": null,
"clicked_at": null
}
}
email.unsubscribed
{
"event": "email.unsubscribed",
"timestamp": "2025-01-10T12:15:00Z",
"newsletter": {
"id": 123,
"name": "Tech Weekly",
"subdomain": "tech-weekly"
},
"data": {
"delivery_id": 789,
"issue_id": 42,
"email": "[email protected]",
"opened_at": "2025-01-10T12:05:00Z",
"clicked_at": null
}
}
Webhook Security
Signature Verification
Each webhook includes a signature header for verification:
X-Webhook-Signature: sha256=abc123...
Verify the signature:
require 'openssl'
def verify_webhook(payload, signature, secret)
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
'sha256',
secret,
payload
)
Rack::Utils.secure_compare(expected, signature)
end
Additional Headers
Each webhook request also includes:
| Header | Description |
|---|---|
X-Webhook-Event |
The event name (e.g. subscriber.created) |
X-Webhook-Delivery-Id |
Unique delivery ID for deduplication |
X-Webhook-Timestamp |
Unix timestamp of the delivery attempt |
Retry Policy
Failed webhooks are retried up to 3 times with polynomial backoff. After all retries are exhausted, the delivery is marked as failed.
Endpoints that accumulate 10 consecutive failures are automatically disabled.
Success Response
Return a 2xx status code to acknowledge receipt:
HTTP/1.1 200 OK
{"received": true}
Managing Webhooks via API
List Webhooks
GET /api/v1/webhook_subscriptions
Create Webhook
POST /api/v1/webhook_subscriptions
{
"url": "https://your-app.com/webhooks",
"events": ["subscriber.created", "issue.sent"]
}
Supported events: subscriber.created, subscriber.updated, subscriber.deleted, issue.sent, issue.published, email.opened, email.clicked, email.bounced, email.unsubscribed
Response (201 Created)
{
"id": 42,
"url": "https://your-app.com/webhooks",
"events": ["subscriber.created", "issue.sent"],
"enabled": true,
"secret": "a1b2c3d4e5f6...",
"created_at": "2025-01-10T12:00:00Z"
}
secret is only returned once at creation. Use it to verify webhook signatures. If you lose it, rotate it via the endpoint below.
Update Webhook
PATCH /api/v1/webhook_subscriptions/:id
{
"url": "https://your-new-endpoint.com/webhooks",
"events": ["subscriber.created", "issue.sent"]
}
Both fields are optional — omit any you don't want to change.
Response (200 OK)
{
"id": 42,
"url": "https://your-new-endpoint.com/webhooks",
"events": ["subscriber.created", "issue.sent"],
"enabled": true,
"created_at": "2025-01-10T12:00:00Z"
}
Rotate Secret
Generate a new signing secret for a webhook. The old secret is immediately invalidated.
POST /api/v1/webhook_subscriptions/:id/rotate_secret
Response
{
"id": 42,
"url": "https://your-app.com/webhooks",
"events": ["subscriber.created", "issue.sent"],
"enabled": true,
"secret": "new_secret_here...",
"created_at": "2025-01-10T12:00:00Z"
}
Delete Webhook
DELETE /api/v1/webhook_subscriptions/:id
Returns 204 No Content on success.
Testing Webhooks
Use the dashboard to send test events:
- Go to Settings → Webhooks
- Click "Test" on any webhook
- Select an event type
- Click "Send Test"
Or use a tool like webhook.site to inspect payloads.
Still need help?
Can't find what you're looking for? Chat with our AI assistant or create a support ticket.
Sign in to get support