The Webhook Reliability Problem (And How to Solve It)
Webhooks are fire-and-forget by design. That's the problem. Here's how to make them durable.
Webhooks: The Internet's Most Fragile Integration
Webhooks power the modern internet. Stripe sends you payment events. GitHub sends you push notifications. Shopify sends you order updates. But here's the dirty secret: webhooks have no delivery guarantee.
The webhook provider sends an HTTP POST to your endpoint. If your server is down, the request fails. Most providers retry a few times (Stripe retries over 72 hours, GitHub retries for 3 days). But if you're sending webhooks to your customers? You're on your own.
The Five Failure Modes
1. Receiver Is Down
Your customer's webhook endpoint returns 503. Without retries, the event is lost. With naive retries, you might overwhelm them when they come back online.
2. Timeout
The receiver takes 35 seconds to process the webhook. Your HTTP client times out at 30 seconds. The webhook was actually processed, but you think it failed. You retry, causing a duplicate.
3. Race Conditions
You send two webhooks in quick succession: "order.created" and "order.updated". The receiver processes them out of order. Their database now shows stale data.
4. Your Server Crashes Mid-Send
You queue 100 webhooks. After sending 47, your server restarts. The remaining 53 are lost because they were only in memory.
5. Rate Limiting
Your customer's endpoint returns 429. You need to back off, but your webhook queue doesn't support per-destination rate limiting.
The Solution: Durable Webhook Delivery
Each of these problems is solved by treating webhook delivery as a durable operation:
// Send a webhook durably
const { call_id } = await fetch("https://api.fetchapi.dev/v1/fetch", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_KEY",
"Idempotency-Key": "order_123_created"
},
body: JSON.stringify({
url: "https://customer-webhook.example.com/events",
method: "POST",
headers: { "X-Webhook-Secret": "whsec_..." },
body: {
event: "order.created",
data: { id: "order_123", total: 99.99 }
},
retry: { maxAttempts: 5, backoff: "exponential" }
})
}).then(r => r.json());FetchAPI solves each failure mode:
| Failure Mode | How FetchAPI Handles It |
|---|---|
| Receiver down | Retries with exponential backoff (configurable up to 10 attempts) |
| Timeout | Tracks each attempt with status codes — no ambiguity |
| Race conditions | Idempotency keys prevent duplicate processing |
| Server crash | State is persisted durably before execution begins |
| Rate limiting | Per-org concurrency limits prevent thundering herd |
Building a Webhook System
If you're building a SaaS that sends webhooks to customers, here's the pattern:
- Event occurs in your system (order created, payment received)
- You POST to FetchAPI with the customer's webhook URL and the event payload
- FetchAPI delivers durably with retries and backoff
- You (optionally) receive a callback when delivery succeeds or fails
- Your customer checks their dashboard to see delivery status and retry manually if needed
What About Svix / Hookdeck?
Specialized webhook delivery services are great if webhooks are your only problem. But most developers also need durable API calls, background job execution, and long-running workflows. FetchAPI handles all of these with the same simple API.
Make your webhooks unbreakable
FetchAPI delivers webhooks durably with one API call. Retries, backoff, idempotency — all built in.
Get Started Free