Failover Destinations
Failover destinations are backup endpoints that automatically receive webhook deliveries when the primary destination fails. This ensures critical webhooks are never lost, even during outages.
Warning
Paid plans only
Setting failoverDestinationIds to a non-empty list requires Starter, Pro or Business. On the Free
plan, POST /api/routes, PATCH /api/routes/{id} and the bulk and import endpoints all return
403:
{
"error": "Failover destinations are only available on paid plans. Please upgrade to access this feature.",
"code": "FEATURE_DISABLED"
}The gate only fires when the list is non-empty, so clearing failover with
"failoverDestinationIds": [] works on any plan.
How It Works
- A delivery attempt to the primary destination fails
- The delivery is retried on the same destination, with backoff
- Once the attempt count reaches
failoverAfterAttempts, the delivery is re-queued against the first failover destination as a new delivery, with a fresh attempt counter and the original delivery recorded as its parent - If that destination also exhausts its attempts, the delivery moves to the second failover destination, then the third. Failover destinations are tried one at a time, in the order you listed them — never in parallel
- When the last one is exhausted the delivery moves to the dead letter queue, with the reason
All failover destinations exhausted (N destinations tried)
Configuration
Add failover destinations to a route:
curl -X PATCH https://api.hookbase.app/api/routes/44444444-4444-4444-8444-444444440002 \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"failoverDestinationIds": ["22222222-2222-4222-8222-222222220017", "22222222-2222-4222-8222-222222220019"],
"failoverAfterAttempts": 3
}'Fields
| Field | Type | Default | Description |
|---|---|---|---|
failoverDestinationIds | string[] | [] | Backup destination IDs (max 3) |
failoverAfterAttempts | number | 5 | Failed attempts before switching to failover (1-5) |
Limits
- Maximum 3 failover destinations per route
- Failover destinations must be different from primary destinations
- Failover destinations can have their own retry policies
Failover Behavior
When Failover Activates
Failover activates after the configured number of consecutive failed attempts on the primary destination. A single success resets the failure counter.
Attempt 1: ❌ Failed (500) → counter: 1
Attempt 2: ❌ Failed (timeout) → counter: 2
Attempt 3: ❌ Failed (503) → counter: 3 = failoverAfterAttempts
→ Failover activated: delivery sent to backup destinationsFailover Headers
Warning
A failover delivery is not marked on the wire
A failover delivery carries exactly the headers the failover destination is configured with. There
is no X-Hookbase-Failover header, and no header naming the primary destination or the error that
caused the switch — an earlier version of this page documented three, and none of them are sent.
To tell failover traffic apart, give the failover destination a custom header of your own, or point
it at a different path. To see that a failover happened, read the delivery in the dashboard or
through GET /api/deliveries: the failover delivery records the
original as its parent, and the original's error message names the destination it failed over to.
Failover Retry Policy
A failover delivery gets the same retry budget as the primary attempt did: up to 5 attempts, with the platform backoff of 10s, 30s, 120s, then 600s between them. A destination's own retry policy does not change this. If a failover destination exhausts those attempts, the next failover destination is tried, and after the last one the delivery enters the dead letter queue.
Interaction with Circuit Breaker
The two features do not chain: an open circuit does not activate failover.
- Circuit closed — normal delivery with retries, and failover after
failoverAfterAttemptsas described above - Circuit opens — every delivery for the route is skipped and moved straight to the dead letter
queue with the reason
circuit_breaker_open. Failover destinations are not tried, because the delivery is never attempted at all - Circuit half-open — probe deliveries go to the primary destination. A probe that fails re-opens the circuit
- Circuit closes — after enough successful probes, normal delivery resumes
Primary ──[fails]──→ retries ──→ failover 1 ──→ failover 2 ──→ DLQ
│
└──[failureThreshold]──→ Circuit Opens ──→ DLQ (circuit_breaker_open)Warning
Order the two thresholds deliberately
The circuit breaker's failureThreshold counts consecutive failures across the route, while
failoverAfterAttempts counts attempts on one delivery. If the circuit opens first, failover never
runs and the deliveries queue up in the DLQ instead — where they can be replayed once the circuit
closes. Set failureThreshold well above failoverAfterAttempts if you want failover to be the
thing that happens.
Examples
Basic Failover Setup
# Create a backup destination
curl -X POST .../destinations \
-d '{
"name": "Backup Webhook Handler",
"slug": "backup-webhook-handler",
"url": "https://backup.yourapp.com/webhooks",
"timeoutMs": 15000
}'
# Add failover to an existing route
curl -X PATCH .../routes/44444444-4444-4444-8444-44444444000c \
-d '{
"failoverDestinationIds": ["22222222-2222-4222-8222-222222220016"],
"failoverAfterAttempts": 3
}'Multi-Region Failover
curl -X PATCH .../routes/44444444-4444-4444-8444-44444444000f \
-d '{
"failoverDestinationIds": ["22222222-2222-4222-8222-222222220035", "22222222-2222-4222-8222-22222222001e", "22222222-2222-4222-8222-222222220015"],
"failoverAfterAttempts": 2
}'Handling Failover in Your Application
Hookbase does not mark a failover delivery, so mark it yourself: give the failover destination a custom header the primary does not have.
curl -X POST .../destinations \
-d '{
"name": "Backup Webhook Handler",
"slug": "backup-webhook-handler",
"url": "https://backup.yourapp.com/webhooks",
"headers": { "X-Delivery-Path": "failover" }
}'app.post('/webhooks', (req, res) => {
// Set on the failover destination, not by Hookbase: there is no
// X-Hookbase-Failover header.
const isFailover = req.headers['x-delivery-path'] === 'failover';
if (isFailover) {
console.log('Received failover delivery');
}
// Process the webhook normally
processWebhook(req.body);
res.status(200).json({ received: true });
});Related
- Routes API — Configure failover fields
- Circuit Breaker — Automatic failure detection
- Pipeline Architecture — Where failover fits in the flow
- Notification Channels — Get alerted on failover events