Back to Blog
Webhooks7 min read

The Webhook Reliability Problem (And How to Solve It)

F
FetchAPI Team
Engineering

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 ModeHow FetchAPI Handles It
Receiver downRetries with exponential backoff (configurable up to 10 attempts)
TimeoutTracks each attempt with status codes — no ambiguity
Race conditionsIdempotency keys prevent duplicate processing
Server crashState is persisted durably before execution begins
Rate limitingPer-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:

  1. Event occurs in your system (order created, payment received)
  2. You POST to FetchAPI with the customer's webhook URL and the event payload
  3. FetchAPI delivers durably with retries and backoff
  4. You (optionally) receive a callback when delivery succeeds or fails
  5. 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