3 second webhook limit
API gateway timeout limit
asynchronous event processing
asynchronous webhook handling
backend API latency
cloudflare worker webhook buffer
database query latency webhooks
decoupling webhooks from database
DNS lookup latency webhooks
event driven architecture webhooks
fast webhook ingestion API
fast webhook response
github webhook timeout
HTTP 202 accepted webhooks
instant webhook acknowledgment
InstaWebhook buffer
kafka webhook buffer
low latency webhook receiver
message queue webhooks
microservices webhook handling
non blocking webhook handler
optimizing webhook response time
preventing webhook timeouts
rabbitmq webhooks
redis webhook queue
reliable webhook delivery
scaling webhook receivers
serverless webhook timeouts
shopify webhook timeout limit
slack webhook timeout
stripe webhook timeout limit
TLS handshake webhooks
twilio webhook timeout
webhook 10 second timeout
webhook architecture best practices
webhook developer guide
webhook endpoint latency
webhook failure points
webhook HTTP 504 gateway timeout
webhook infrastructure design
webhook ingestion buffer
webhook integration troubleshooting
webhook latency fix
webhook listener architecture
webhook payload acknowledgment
webhook payload receiver speed
webhook performance optimization
webhook processing pipeline
webhook queue processing
webhook rate limiting
webhook request delivery failure
webhook response 200 OK fast
webhook retry mechanism
webhook timeout 504 error
webhook timeout limit
The 10 Second Budget Why Webhooks Fail Before Your Code Even Runs
The 10-Second Budget: Why Webhooks Fail Before Your Code Even Runs
When developers build event-driven systems, they often make a quiet assumption: "If my code processes the payload in under a couple of seconds, my webhook endpoint will succeed."
In production, that assumption falls apart. Webhooks fail silently, trigger cascading retries, or get disabled by the provider entirely — even when your application code runs fast.
The reason is that your code never gets a clean slate. It's operating inside a strict response window enforced by the provider — Slack, Shopify, GitHub, Stripe, Twilio, and nearly everyone else. Whether it's a 10-second ceiling or a brutal 3-second one, this window is a hard boundary. By the time your route handler receives the payload, a hidden waterfall of network hops, cryptographic checks, and runtime startup has already burned through a meaningful chunk of your budget.
This piece breaks down the anatomy of a webhook request, the real, current timeout numbers for major providers, where hidden latency actually accumulates in 2026, and why decoupling ingestion from processing is still the fix — using real, verifiable infrastructure rather than a made-up product name.
The Reality of Provider Timeout Windows
Third-party platforms send huge volumes of webhooks, so they enforce aggressive timeouts to protect their own infrastructure from hanging sockets. If your server doesn't return a 2xx status before the deadline, the provider marks the delivery failed.
Here's what's actually documented today, not the vaguely-remembered numbers that circulate in blog posts:
Provider Response Window Retry Behavior What Happens on Persistent Failure
Slack 3 seconds (hard, non-configurable) Up to 3 automatic retries; with "Delayed Events" enabled, additional hourly retries for 24h Retries stop; the x-slack-retry-reason header tells you why a retry fired
Shopify 5 seconds total (plus a separate 1-second connection timeout) Up to 19 retries spread over 48 hours Endpoint is automatically disabled after sustained failures over that window
GitHub 10 seconds No automatic redelivery. A timed-out delivery is simply marked failed — you must redeliver manually or via the API GitHub does not auto-disable webhooks for failed deliveries (it only auto-disables ones restricted by OAuth app access policies)
Stripe ~20 seconds (commonly cited figure; Stripe's own guidance is simply "respond before any logic that could cause a timeout") Up to ~16 attempts with exponential backoff over roughly 3 days Email alerts, then the endpoint is disabled after continuous failure across that window
Twilio 15 seconds for Voice/SMS request URLs; 5 seconds for Conversations webhooks Voice/SMS request-URL webhooks are not retried — Twilio falls straight to your configured Fallback URL. Status callbacks are retried, up to 3 times with backoff No fallback configured means the inbound event is simply dropped
A few corrections worth calling out explicitly, because they get repeated incorrectly all over the web:
GitHub does not automatically retry failed deliveries. If your handler times out, that event is gone until you manually redeliver it via the dashboard or the REST API — GitHub explicitly recommends writing a scheduled script to poll for and redeliver failed events.
Twilio doesn't "retry" incoming message and call webhooks in the usual sense — it fails over to a separate Fallback URL immediately. Only status callbacks get real retries.
Shopify's connection timeout (1 second) is tighter than its total response timeout (5 seconds). Your server has to accept the TCP connection in under a second, which is easy to miss if you're only measuring handler execution time.
The Hidden Latency Waterfall: Where the Budget Actually Disappears
An incoming HTTP POST doesn't materialize instantly inside your route handler. Between the provider emitting the event and line 1 of your function executing, the request crosses several layers:
Code example
Copy code
- DNS Resolution ~ 20ms - 100ms
- TCP + TLS 1.3 Handshake ~ 50ms - 250ms
- Ingress / Load Balancer routing ~ 10ms - 50ms
- Cold start (if serverless, see below) ~ 0ms - 3,000ms+ (highly variable)
- Signature verification + body parsing ~ 10ms - 100ms
- Database connection pool acquisition ~ 10ms - 1,000ms+ under load The single biggest variable here — and the one most articles get outdated on — is cold starts.
Cold starts are much less scary than they used to be (with big caveats)
The "500ms–3,000ms" cold start figure that circulates constantly is stale for the common case. Current production benchmarks on AWS Lambda in 2026 put Node.js and Python cold starts around 200–400ms at P50 on arm64 (Graviton), and AWS reports cold starts occurring in under 1% of invocations for warm-traffic workloads. VPC-attached cold starts, which used to add 10+ seconds, were fixed back in 2019. Rust and custom runtimes can cold-start in under 20ms.
The caveat: Java and .NET are still slow to cold-start — multiple seconds without mitigation — unless you use something like AWS Lambda SnapStart, which brings Java down from roughly 5 seconds to under 200ms in AWS's own benchmarks. And cold starts still matter disproportionately for webhooks specifically, because a webhook consumer that only fires a few times an hour is exactly the profile that gets recycled between invocations, unlike a busy user-facing API that stays warm.
Practical takeaway: don't assume cold starts are dead, but also don't budget 3 seconds for them if you're running Node.js or Python on modern Lambda without a VPC. Measure your actual P95/P99 init duration instead of using a rule of thumb.
Database connection pool starvation is still very real
Code example
Copy code
// Common anti-pattern
app.post('/api/webhooks/stripe', async (req, res) => {
verifySignature(req);
const dbClient = await pool.connect(); // <-- hangs here under load
const customer = await dbClient.query('SELECT * FROM users WHERE stripe_id = $1', [req.body.customer]);
// ...business logic...
res.status(200).send('OK');
});
If traffic spikes and your connection pool is exhausted, your handler blocks waiting for a socket. That wait counts against your timeout budget just as much as slow business logic does.
The Cascade of Failure
When a webhook does time out, the fallout goes well beyond one failed HTTP call:
Code example
Copy code
Webhook times out
│
▼
Provider assumes delivery failed
│
▼
Provider retries (schedule varies wildly by provider — see table above)
│
┌────┴────┐
▼ ▼
Duplicate Out-of-order
processing processing
│ │
└────┬────┘
▼
Endpoint disabled after sustained failure (Slack, Shopify, Stripe)
Phantom processing. A timed-out connection doesn't stop your server from finishing the work it already started. The provider marks it failed even though your database write eventually succeeds.
Duplicates. Because the provider thinks the first attempt failed, a retry arrives later and your server processes the same event twice — double-charged cards, duplicate emails, overwritten records — unless you have real idempotency handling.
Out-of-order delivery. None of the major providers guarantee ordered delivery on retry. An order.updated event stuck in a retry queue can arrive after a later order.cancelled event succeeds on the first try.
Automatic disablement. Shopify and Stripe will both automatically disable an endpoint after it fails consistently over their respective windows (48 hours and roughly 3 days). GitHub, notably, will not — but it also won't retry for you, so silent data loss is the actual risk there instead.
The Architectural Flaw: Treating Webhooks Like a REST API
The root cause is usually structural. A normal REST client waits synchronously for a result:
Code example
Copy code
Client Request → Business Logic → Database Query → HTTP 200 Response
Applying that pattern to webhooks means asking an external service to hold a TCP socket open while you parse JSON, hit three tables, generate a PDF, and post to Slack. The provider doesn't care about your business logic outcome — it only cares whether you received the payload.
The Fix: Decouple Ingestion from Processing
Code example
Copy code
Webhook Provider (Stripe/Shopify/etc.)
│ 1. HTTP POST
▼
┌───────────────────────────────┐
│ Ingestion layer (edge/API) │
└───────────────────────────────┘
│ │
│ 2. Push to queue │ 3. Return 2xx/202 immediately
▼ ▼
┌────────────────┐ Provider closes socket, happy
│ Durable queue │
│ (Redis/SQS/ │
│ Kafka) │
└────────────────┘
│ 4. Async pull
▼
┌───────────────────────────────┐
│ Background worker layer │
│ (DB writes, 3rd-party calls, │
│ PDF generation, etc.) │
└───────────────────────────────┘
The golden rule: acknowledge receipt immediately with a 2xx/202, then process asynchronously.
Building a Low-Latency Ingestion Buffer
There are two realistic approaches: run your own queue, or use a managed webhook ingestion gateway.
Approach 1: Self-hosted queue (Redis + BullMQ, or SQS)
Code example
Copy code
// Node.js / Express — ingestion-only handler
import express from 'express';
import { Queue } from 'bullmq';
const app = express();
const webhookQueue = new Queue('webhook-processing', { connection: redisConfig });
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
try {
if (!verifyStripeSignature(req.body, sig, process.env.STRIPE_SECRET)) {
return res.status(400).send('Invalid Signature');
}
} catch {
return res.status(400).send('Signature Verification Failed');
}
// No DB calls here — just push to the queue
await webhookQueue.add('stripe-event', {
rawPayload: req.body.toString(),
receivedAt: Date.now(),
}, {
attempts: 5,
backoff: { type: 'exponential', delay: 1000 },
});
return res.status(202).json({ received: true }); // Typically 10-40ms
});
This works well and keeps you in full control, but you own the operational overhead: queue infrastructure, backpressure handling, dead-letter queues, replay tooling, and observability.
Approach 2: A managed webhook ingestion gateway
Rather than build all of that yourself, several real, currently-maintained products specialize in exactly this problem — receiving webhooks at the edge, acknowledging instantly, and feeding your backend at a rate it can handle:
Hookdeck — purpose-built for inbound webhook orchestration: durable queueing with backpressure, 100+ pre-configured provider sources, filtering, transformations, and replay. Closed-source, cloud-only.
Svix Ingest — the inbound counterpart to Svix's outbound webhook-delivery platform (Svix Dispatch, which co-authored the Standard Webhooks spec). A good fit if you're already using Svix to send webhooks to your own customers, or need HIPAA/PCI/CCPA-ready infrastructure out of the box.
Convoy — a fully open-source webhooks gateway that handles both inbound and outbound delivery, useful if self-hosting is a hard requirement.
Hook0 — open-source (SSPL-1.0), EU-hosted, GDPR-oriented; more focused on outbound webhook delivery to your own customers than inbound ingestion.
AWS EventBridge Pipes / API destinations, Upstash QStash, or plain SQS — cloud-native, lower-level building blocks if you want managed queueing without a dedicated webhooks product.
The pattern is the same regardless of vendor: a globally-distributed ingress layer does signature verification and returns a 2xx in single-digit-to-low-double-digit milliseconds, then streams the payload to your backend asynchronously, with retries and dead-letter handling built in. Which one fits depends on whether you mainly need to receive webhooks reliably (Hookdeck, Svix Ingest), send webhooks to your own customers (Svix Dispatch, Hook0), or want to self-host end to end (Convoy, Hook0).
Best Practices Checklist
Separate routing from execution. Don't query your primary database inside the webhook route. Validate the signature, push to a queue, return 202.
Optimize network ingress. Host close to the provider's origin, or route through an edge layer with HTTP/2 and TLS session resumption to avoid redundant handshakes.
Handle cold starts realistically, not by rule of thumb. On modern Lambda (Node.js/Python, arm64, no VPC), cold starts are typically sub-500ms — measure yours instead of assuming worst-case numbers from 2019-era benchmarks. If you're on Java or .NET, use SnapStart or provisioned concurrency.
Implement real idempotency, keyed off the provider's own event ID:
Stripe: the event's id (e.g. evt_1N...)
GitHub: the X-GitHub-Delivery header
Shopify: the X-Shopify-Webhook-Id header
Twilio: the I-Twilio-Idempotency-Token header (on supported products)
Store processed IDs in a fast cache with a TTL of 24–72 hours to drop duplicate retries before they touch your business logic.
- Monitor P95/P99, not averages. A 3-second average response time can hide a P99 that's blowing past Slack's 3-second hard limit on a meaningful slice of traffic.
Before vs. After
❌ Synchronous processing (timeout-prone):
Code example
Copy code
@app.post("/webhooks/shopify")
async def handle_shopify_webhook(request: Request):
payload = await request.json()
conn = psycopg2.connect(DATABASE_URL)
cursor = conn.cursor()
cursor.execute("SELECT * FROM inventory WHERE product_id = %s", (payload['product_id'],))
item = cursor.fetchone()
time.sleep(4.5) # simulating real business logic — risks Shopify's 5s window
cursor.execute("UPDATE inventory SET stock = stock - 1 WHERE product_id = %s", (payload['product_id'],))
conn.commit()
return {"status": "success"}
✅ Decoupled async ingestion:
Code example
Copy code
@app.post("/webhooks/shopify")
async def handle_shopify_webhook(request: Request):
raw_body = await request.body()
hmac_header = request.headers.get("X-Shopify-Hmac-SHA256")
if not verify_shopify_hmac(raw_body, hmac_header):
return Response(status_code=status.HTTP_401_UNAUTHORIZED)
event_data = {
"event_id": request.headers.get("X-Shopify-Webhook-Id"),
"payload": raw_body.decode("utf-8"),
}
r.lpush("shopify_webhook_queue", json.dumps(event_data))
return Response(status_code=status.HTTP_202_ACCEPTED) # ~10-15ms
Conclusion
The response window webhook providers give you isn't generous — it's a safety ceiling, and the numbers above are the real, currently-documented ones, not the rounded-off figures that get copy-pasted across the internet. Once you account for network latency, TLS handshakes, occasional cold starts, and database contention, your business logic rarely gets the full window.
The fix hasn't changed even as the numbers have: acknowledge immediately, process asynchronously, and put a real queue — self-hosted or managed — between the provider and your business logic.
Sources
Slack: Guide to Slack Webhooks
Shopify: Webhook Best Practices (Shopify Engineering)
Shopify webhook review — Svix
GitHub: Handling failed webhook deliveries
GitHub: Best practices for using webhooks
Stripe: Receive Stripe events in your webhook endpoint
Stripe webhook guide — Hookdeck
Twilio: Webhooks connection overrides
Twilio error 11205: HTTP connection failure
Twilio webhook troubleshooting — Hooklistener
Lambda cold start benchmarks, 2026
Lambda cold start optimization guide
Webhook infrastructure comparison, 2026 — Nango
Hookdeck Event Gateway vs Svix Ingest
Top comments (0)