Delivery & Retry Logic
Zyphr delivers webhook events reliably with configurable retry policies and automatic circuit breaker protection.
How Delivery Works
Event Produced → RabbitMQ Queue → Webhook Worker → HTTP POST → Customer Endpoint
↓ ↓
30s timeout 2xx = success
non-2xx = schedule retry
- When an event occurs (email delivered, user created, etc.), Zyphr finds all active webhooks subscribed to that event type.
- A delivery record is created for each matching webhook and published to the message queue.
- The webhook worker picks up the delivery job and sends an HTTP POST to your endpoint.
- Your endpoint has 30 seconds to respond with a
2xxstatus code. - On failure, the delivery is retried according to the webhook's retry policy.
Default Retry Policy
New webhooks use the following default retry schedule:
| Attempt | Delay After Failure | Cumulative Wait |
|---|---|---|
| 1 | Immediate | — |
| 2 | 1 minute | 1 minute |
| 3 | 5 minutes | 6 minutes |
| 4 | 30 minutes | 36 minutes |
| 5 | 2 hours | 2 hours 36 minutes |
| 6 | 12 hours | 14 hours 36 minutes |
After 6 attempts (default), the delivery status is set to exhausted.
Custom Retry Policies
Configure a custom retry policy when creating or updating a webhook:
curl -X POST https://api.zyphr.dev/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks",
"events": ["email.delivered"],
"retry_policy": {
"max_attempts": 3,
"intervals": [60, 300, 900]
}
}'
| Parameter | Type | Constraints | Default |
|---|---|---|---|
max_attempts | integer | 1–10 | 6 |
intervals | integer[] | Each value in seconds, max 10 entries | [60, 300, 1800, 7200, 43200, 86400] |
If the number of retries exceeds the intervals array length, the last interval is repeated.
Delivery Statuses
| Status | Description |
|---|---|
pending | Delivery is queued and waiting to be sent |
success | Endpoint returned a 2xx response |
failed | Delivery failed, retry scheduled |
exhausted | All retry attempts exhausted |
Delivery Headers
Every webhook delivery includes the following HTTP headers. The exact headers depend on the webhook's header_format setting.
Standard Webhooks Format (header_format: "standard")
| Header | Description | Example |
|---|---|---|
Content-Type | Always application/json | application/json |
User-Agent | Zyphr user agent | Zyphr-Webhook/1.0 |
webhook-id | Stable message ID for deduplication | msg_a1b2c3d4e5f6... |
webhook-timestamp | Unix timestamp (seconds) | 1707307200 |
webhook-signature | HMAC-SHA256 signature | v1,K5oZfzN... |
Legacy Format (header_format: "legacy")
| Header | Description | Example |
|---|---|---|
Content-Type | Always application/json | application/json |
User-Agent | Zyphr user agent | Zyphr-Webhook/1.0 |
X-Zyphr-Signature | HMAC-SHA256 hex signature | sha256=a1b2c3... |
X-Zyphr-Timestamp | Unix timestamp (seconds) | 1707307200 |
X-Zyphr-Delivery-Id | Delivery record ID | del_uuid |
X-Zyphr-Event-Type | Event type | email.delivered |
Set header_format to "both" to receive both sets of headers during a migration period.
Circuit Breaker (Auto-Disable)
Zyphr automatically disables webhooks that consistently fail:
- Threshold: 10 consecutive delivery failures across any deliveries to that webhook
- Action: Webhook status changes to
disabled - Reason: Recorded in
disabled_reasonfield - Recovery: Manually re-enable by setting
statusto"active"via the API — this resets the failure counter
# Re-enable a disabled webhook
curl -X PUT https://api.zyphr.dev/v1/webhooks/YOUR_WEBHOOK_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }'
The consecutive failure counter resets to zero on any successful delivery.
Manual Retry
Retry a failed or exhausted delivery manually:
curl -X POST https://api.zyphr.dev/v1/webhooks/YOUR_WEBHOOK_ID/deliveries/DELIVERY_ID/retry \
-H "Authorization: Bearer YOUR_API_KEY"
This resets the delivery to pending with zero attempts and queues it for immediate delivery. Only deliveries with status failed or exhausted can be retried.