How to Prevent Webhook Traffic Spikes from Crashing Your API
How to Prevent Webhook Traffic Spikes from Crashing Your API If you operate an API in 2026, you live in an event-driven world.

How to Prevent Webhook Traffic Spikes from Crashing Your API
If you operate an API in 2026, you live in an event-driven world. Webhooks aren't a convenience feature anymore — they're the backbone of real-time commerce, CI/CD pipelines, and asynchronous AI-agent workflows. That reliance has a dark side: the accidental self-inflicted DDoS.
When a major platform like GitHub, Shopify, or Stripe hits a network partition, runs a huge sales event, or simply clears a backlog of delayed events, it can fire tens of thousands of webhook POST requests at your servers in a very short window. If your infrastructure takes that hit without structural safeguards, your database connection pool exhausts, memory maxes out, and the API goes down — and if your retry handling is naive, the recovery can be almost as damaging as the original spike.
This guide covers the real mechanics of that failure mode, the algorithms used to defend against it, how major providers actually behave under load (some surprising details here), and where a managed ingress layer fits into the picture.
The 2026 Reality: Webhook Floods Aren't Hypothetical
Unlike normal API traffic, where your client dictates the pace of requests, webhooks reverse the control flow — you don't control the producer. When a large platform accumulates a backlog, its own queueing systems often don't resume gently; they catch up as fast as possible.
This isn't a theoretical scenario. On April 28, 2026, Shopify experienced a webhook delivery latency incident: normal delivery latency sits around 2 seconds, but for roughly 8 hours that day, some merchants saw delays stretching into minutes and, in places, over an hour, as the platform worked through a backlog rather than dropping events outright. Merchants noticed orders not flowing into fulfillment systems and inventory updates lagging before anyone had identified the root cause. The incident became a named example in the webhook-infrastructure community of what's now called a "recovery surge" — the burst of traffic that arrives after a producer's outage clears, which can be just as dangerous as the outage itself.
What happens to an unprotected endpoint
- Connection exhaustion — your web server accepts thousands of incoming TCP connections at once, consuming available worker threads.
- Synchronous processing bottleneck — if your handler parses JSON, verifies signatures, and writes to a database before responding, every request stays open the whole time.
- Database lockup — concurrent writes exhaust your connection pool; query latency spikes and the database backs up.
- Cascading failure — your server starts timing out, the provider schedules a retry (or gives up, depending on the provider — more on that below), and the backlog grows instead of shrinking.
Structural Safeguards: Token Bucket and Leaky Bucket
Token bucket is the standard approach for graceful rate limiting. Tokens refill a bucket at a fixed rate (say, 10/second) up to some cap (say, 100). Every incoming request spends a token; an empty bucket means an immediate 429 Too Many Requests. This lets you absorb small bursts instantly while enforcing a strict long-run average — this is genuinely how a lot of real APIs behave. Shopify's own API rate limiting, for instance, is a bucket-style model: a fixed sustained rate with a defined burst allowance on top, so short spikes don't immediately trip the limit as long as your average stays in bounds.
Leaky bucket controls the rate of processing rather than acceptance — it's effectively a queue with a constant drain rate. Requests can arrive at any speed, but they're released to your processing logic at a fixed pace; if the queue overflows, excess requests are discarded or rejected. This is useful for smoothing bursty inbound traffic into a flat, predictable load on your database.
Circuit Breakers: Protecting What's Downstream
Rate limiting protects your web server's front door; a circuit breaker protects everything behind it — your database, your third-party API calls, your microservices. It monitors failure rates on a downstream dependency and moves through three states:
- Closed — normal traffic flow, failures are monitored.
- Open — once failures cross a threshold (e.g., a defined error rate over a rolling window), the breaker trips and requests fail fast without hitting the struggling dependency, giving it room to recover.
- Half-open — after a cooldown, a small trickle of traffic is let through to test recovery; success closes the circuit again, failure re-opens it.
One correction worth making here: Netflix's Hystrix, long the reference implementation for this pattern, was put into maintenance mode back in 2018 and is now considered deprecated. If you're implementing this today, the current standard tooling is Resilience4j (for JVM stacks) or a service mesh like Istio, which offers circuit breaking at the infrastructure layer rather than in application code.
The Retry Amplification Problem
Rejecting traffic with 429s or 5xxs only helps if the sender backs off sensibly. Naive exponential backoff has a known failure mode of its own: if a large batch of requests fails at the exact same instant, they all compute the same retry delay and hammer your recovering endpoint again in near-perfect sync — a thundering herd. This is why production-grade retry logic pairs exponential backoff with jitter (randomizing the delay slightly) so retries spread out instead of re-synchronizing.
This is also, functionally, what caused the visible pain in Shopify's April 2026 incident described above — not the initial slowdown itself, but the surge of queued deliveries arriving all at once once the backlog started draining.
What Retry Behavior Actually Looks Like, Provider by Provider
The original assumption that "every provider retries aggressively" isn't quite right, and it matters for how you design your defenses:
| Provider | Response window | Retry behavior |
|---|---|---|
| Stripe | ~10 seconds for a 2xx | Exponential backoff for up to 3 days in live mode (roughly 16–17 attempts); only 3 attempts over a few hours in test mode. Non-2xx responses (not just 5xx) trigger retries; 4xx is generally treated as terminal. After sustained failure, Stripe disables the endpoint and emails the account owner. |
| Shopify | ~5 seconds for a 2xx | Up to 19 retries over 48 hours with exponential backoff. Shopify's infrastructure is designed to queue rather than drop events when it falls behind, which is exactly what produced the April 2026 recovery surge. |
| GitHub | 10 seconds for a 2xx | No automatic retries at all. A failed delivery just fails — GitHub does not resend it. You can manually redeliver (or use the deliveries API) for events from the past 7 days, but if you don't build your own reconciliation logic, a missed delivery is gone for good. |
The practical takeaway: retry-storm protection matters for Stripe- and Shopify-style providers, but for GitHub-style providers the bigger risk is silent data loss, not amplification — so your resilience plan needs both a burst-absorption strategy and a reconciliation/polling backstop for providers that won't retry on your behalf.
Why Rejecting Traffic Yourself Still Costs You Compute
Token buckets, leaky buckets, and circuit breakers are essential, but there's a structural limit to handling all of this natively in your application server: your infrastructure still has to receive the connection in order to reject it. If a platform sends 50,000 webhooks in a few seconds, your servers still absorb 50,000 TLS handshakes and HTTP header parses, and still make 50,000 checks against whatever store backs your rate limiter, even if every single one ends in a 429. For a lot of mid-sized setups, the compute cost of rejecting traffic is itself enough to cause an outage.
Decoupling Reception from Processing: The Queue-First Pattern
The structural fix is to stop receiving high-variance traffic directly on your application servers at all. Instead, you point the webhook provider at an intermediary that accepts everything instantly, stores it durably, and feeds your API at a pace it can actually sustain. This "queue-first" or "ingress buffer" pattern is a well-established category now, with several managed options — Svix, Hookdeck, Convoy, and InstaWebhook among them — plus DIY equivalents built on something like SQS or a Redis-backed queue in front of your workers.
As one concrete example, InstaWebhook is a webhook gateway in this category. Based on its current documentation, it provides durable endpoint ingestion (payloads are accepted and stored before your server is involved), a visible delivery timeline (received → queued → attempted → retried → delivered/dead-lettered), configurable per-endpoint rate and payload limits, idempotency-key support at the point of ingest, replay of past events, and a "bring your own database" mode for teams that need payload storage to stay inside their own infrastructure for compliance reasons. That's a genuinely different problem than what Stripe/Shopify/GitHub retries solve — it protects your endpoint from your own downstream capacity, independent of whatever retry policy the original sender uses.
Whichever tool you choose (or whether you build it yourself), the properties you're looking for are the same: instant acknowledgment to the sender, durable storage before processing, a controlled drain rate into your systems, and a dead-letter queue for anything that fails repeatedly so an engineer can inspect and replay it rather than losing it.
Best Practices Checklist for 2026
1. Enforce idempotency.
At-least-once delivery is the norm, not the exception — a timeout on your end can mean the sender retries a request that actually succeeded. Log the event ID and short-circuit on duplicates before mutating state. Stripe's own Idempotency-Key mechanism is a good model: a high-entropy key (they recommend a V4 UUID), the first response cached and replayed for repeat requests, with a defined expiry window.
2. Verify signatures — but know what "standard" actually means here.
Almost every major provider (GitHub, Stripe, Shopify) still uses its own bespoke HMAC-SHA256 scheme with provider-specific headers — there isn't yet a single universal signature format across the ecosystem. RFC 9421 (HTTP Message Signatures), published by the IETF in February 2024, is a real standard designed to solve exactly this fragmentation, and it's already used in production by Cloudflare (for its Verified Bots program) and by OpenAI (for ChatGPT agent request verification). But its adoption specifically for webhook delivery is still emerging rather than dominant — don't assume a provider supports it without checking their docs. Whatever scheme is in play, verify the signature against the raw body with a constant-time comparison before doing anything else, and reject invalid signatures immediately with a 401.
3. Use IP allowlisting where it's offered.
GitHub, for example, publishes its current IP ranges via a GET /meta API endpoint rather than a static list, specifically because the ranges change over time — so if you allowlist, refresh periodically rather than hardcoding.
4. Push heavy work to background jobs.
Never run long tasks (PDF generation, image processing, LLM calls) synchronously inside the handler. Verify, enqueue, and return 200/202 immediately.
5. Add jitter to your own retry logic, and build a reconciliation backstop. If you're the one retrying calls to a downstream service, exponential backoff alone isn't enough — add randomized jitter to avoid synchronized retry storms. And for any provider that doesn't retry failed deliveries on your behalf (GitHub being the clearest example), periodically reconcile against the provider's API so a delivery that never arrived still gets caught.
Conclusion
Webhook traffic in 2026 is higher-volume and higher-stakes than it was even a couple of years ago, and the failure mode is rarely the initial spike — it's the recovery surge afterward, or the silent gap left by a provider that doesn't retry at all. Token buckets, leaky buckets, and circuit breakers are the right building blocks, but the highest-leverage architectural change is decoupling reception from processing, whether that's a self-built queue or a managed gateway. Pair that with real idempotency, signature verification appropriate to what your providers actually support, and a reconciliation habit for providers that won't retry for you, and a traffic spike stops being an incident and becomes a Tuesday.
Sources
- RFC 9421 — HTTP Message Signatures, IETF
- Stripe webhooks — retry behavior, Svix resource guide
- GitHub Docs — Best practices for using webhooks
- GitHub Docs — About GitHub's IP addresses
- GitHub Community Discussion — no automatic webhook retries
- Shopify Developer Docs — API limits
- Shopify webhooks developer guide, 2026
- Hookdeck — "The Recovery Surge," on the April 28, 2026 Shopify incident
- Circuit breaker pattern / Hystrix deprecation, Neil Sherman
- InstaWebhook documentation