{"post":{"slug":"webhooks-deep-dive-explained-2026","title":"Webhooks deep dive in 2026: delivery, security, and reliability","excerpt":"Webhooks are HTTP callbacks that push events to your server instead of making you poll. This 2026 guide covers delivery guarantees, signature verification, and the failure modes teams hit in production.","category":"code","date":"April 21, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">W</span>ebhooks are the simplest form of event-driven integration: the service you are integrating with makes an HTTP POST to your server when something happens. No polling loop, no message broker in the critical path, no SDK required on the receiving end. Yet most webhook integrations in production have at least one subtle failure mode — missed events, duplicate processing, or unverified payloads. This is how to get them right.\n\n## What changed in 2026\n\n- **Webhook signatures are now universal.** Stripe, GitHub, Shopify, Svix, and virtually every serious provider sign every payload. The era of accepting raw webhook bodies without verification is over.\n- **Svix and Hookdeck emerged as infrastructure layers** for teams building outbound webhooks — they handle delivery, retries, and the management UI so you do not build it from scratch.\n- **OpenAPI's `webhooks` object** (introduced in 3.1) became widely adopted — webhook contracts are now documented alongside REST endpoints in the same spec file.\n- **Delivery guarantees converged on at-least-once.** No mainstream provider guarantees exactly-once; idempotency on the receiver is the expected pattern.\n\n## How webhook delivery works\n\n```\n1. Event occurs in sender (e.g., payment completed)\n2. Sender queues a delivery job\n3. Delivery job POSTs JSON to your registered endpoint\n4. Your endpoint returns HTTP 200-299\n5. If no 200 within timeout (~5s), sender marks delivery failed\n6. Sender retries with exponential backoff (e.g., 1m, 5m, 30m, 2h, 12h)\n```\n\nThe retry window and max attempts vary by provider — Stripe retries for 3 days, GitHub retries for ~72 hours. Design for this.\n\n## Signature verification\n\nEvery provider gives you a secret when you register the endpoint. They compute `HMAC-SHA256(secret, payload)` and send the result in a header (e.g., `Stripe-Signature`, `X-Hub-Signature-256`).\n\n```python\nimport hmac, hashlib\n\ndef verify_stripe_signature(\n    payload: bytes,\n    header: str,\n    secret: str,\n    tolerance_seconds: int = 300,\n) -> bool:\n    import time\n    parts = {k: v for k, v in (p.split(\"=\", 1) for p in header.split(\",\"))}\n    timestamp = int(parts[\"t\"])\n    if abs(time.time() - timestamp) > tolerance_seconds:\n        return False  # replay attack protection\n    signed = f\"{timestamp}.{payload.decode()}\"\n    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()\n    received = parts.get(\"v1\", \"\")\n    return hmac.compare_digest(expected, received)\n```\n\nKey points:\n- Use `hmac.compare_digest` to prevent timing attacks.\n- Check the timestamp to prevent replay attacks (reject events older than 5 minutes).\n- Read the raw body bytes **before** any JSON parsing — frameworks that parse the body first can invalidate the signature.\n\n## Respond fast, process async\n\n```python\n# FastAPI example — acknowledge immediately, enqueue for processing\nfrom fastapi import FastAPI, Request, BackgroundTasks\n\napp = FastAPI()\n\n@app.post(\"/webhooks/stripe\")\nasync def stripe_webhook(request: Request, background_tasks: BackgroundTasks):\n    payload = await request.body()\n    sig = request.headers.get(\"Stripe-Signature\", \"\")\n    if not verify_stripe_signature(payload, sig, STRIPE_WEBHOOK_SECRET):\n        return {\"error\": \"invalid signature\"}, 400\n    event = json.loads(payload)\n    background_tasks.add_task(process_event, event)  # async, not blocking\n    return {\"received\": True}  # return 200 immediately\n```\n\nTarget under 500ms for the HTTP response. Any work beyond signature verification and enqueueing belongs in a background worker.\n\n## Idempotency — handling duplicates\n\nSenders retry on network failures, even if your server processed the event. You will receive the same event ID twice. Make handlers idempotent:\n\n```python\ndef process_payment_event(event: dict):\n    event_id = event[\"id\"]  # e.g., \"evt_1ABC...\"\n    if db.webhook_events.exists(event_id):\n        return  # already processed, skip\n    with db.transaction():\n        db.webhook_events.insert(event_id, processed_at=now())\n        # ... business logic\n```\n\nUse the event ID as a deduplication key in a `webhook_events` table. The insert inside the transaction ensures exactly-once processing even under concurrent retries.\n\n## Delivery guarantees comparison\n\n| Guarantee | What it means | Who provides it | Your responsibility |\n| --- | --- | --- | --- |\n| At-least-once | Delivered one or more times | Stripe, GitHub, most SaaS | Idempotent handlers |\n| At-most-once | Delivered zero or one time | Rare; fire-and-forget | Accept possible loss |\n| Exactly-once | Delivered exactly once | Nobody via HTTP alone | Idempotency + dedup |\n\n## How to pick your processing architecture\n\n1. **Low volume (<10/sec), low stakes?** Process inline after enqueueing with a simple task queue (Celery, BullMQ, Sidekiq).\n2. **High volume or critical events?** Write to a durable queue (SQS, Kafka) first, then process. The HTTP handler only enqueues.\n3. **Building outbound webhooks for your own platform?** Use Svix or Hookdeck — do not build the retry/delivery infra yourself.\n4. **Debugging failed deliveries?** Log every raw payload with its event ID before processing so you can replay.\n\n## Common mistakes\n\n**Not verifying signatures.** An unverified webhook endpoint accepts arbitrary POST requests from anyone who finds the URL.\n\n**Parsing the body before verification.** Framework middleware that parses JSON before your handler runs modifies the byte stream; the HMAC will not match. Read raw bytes first.\n\n**Blocking on slow processing.** If your handler calls a slow third-party API or does heavy DB work synchronously, the sender times out and retries — creating duplicate events you now must handle.\n\n**No deduplication.** Most teams discover this after a retry storm or a provider bug causes duplicate deliveries.\n\n**Hardcoded endpoint URLs in test environments.** Use ngrok, Cloudflare Tunnel, or a local proxy; do not expose your dev machine directly.\n\n## What to skip\n\n- **Polling as a fallback for missed webhooks** in critical integrations — use the provider's event log API (e.g., Stripe's Events API) for reconciliation instead.\n- **Processing all event types in one handler** — route by `event.type` early and handle unknown types gracefully rather than erroring.\n- **Shared webhook secrets across environments** — issue separate secrets for dev, staging, and production.\n\n## FAQ\n\n**What if my server is down during a webhook?**\nProviders retry for hours to days. After recovery, they catch up automatically. For events older than the retry window, use the provider's event log to backfill.\n\n**How do I test webhooks locally?**\nUse `ngrok http 8000` or the Stripe CLI (`stripe listen --forward-to localhost:8000`). Both tunnel public HTTPS requests to your local server.\n\n**Should I store all webhook payloads?**\nYes, at least for a rolling window (7–30 days). Raw payload logs are invaluable for debugging and replaying after handler bugs.\n\n**What is the difference between a webhook and a WebSocket?**\nA webhook is a one-way HTTP callback triggered by an event — stateless, fire-and-forget from the sender's perspective. A WebSocket is a persistent bidirectional connection maintained by both parties.\n\n## Where to go next\n\n- [Idempotency explained in 2026](/blog/idempotency-explained-2026)\n- [API authentication explained in 2026](/blog/api-authentication-explained-2026)\n- [How to handle errors gracefully in 2026](/blog/how-to-handle-errors-gracefully-2026)","hue":314,"metaTitle":"Webhooks Deep Dive 2026: Delivery, Security, Reliability","metaDescription":"Webhooks explained in 2026: how delivery works, HMAC signature verification, retry handling, idempotency, and the patterns that keep integrations reliable.","keywords":["webhooks explained 2026","webhook security signature verification","hmac webhook validation","webhook retry handling","idempotent webhook processing","webhook vs polling 2026","webhook delivery guarantees","at least once delivery webhooks","stripe webhook handling","webhook best practices 2026"],"tldr":["**Webhooks push; polling pulls** — the sender calls your endpoint when something happens, reducing latency and wasted requests.","**Always verify the signature** — every serious webhook provider signs payloads with HMAC-SHA256; validate before trusting the body.","**Respond 200 fast, process async** — return HTTP 200 within 5 seconds or the sender will retry; do heavy work in a background queue.","**Design for at-least-once delivery** — retries mean you will receive duplicate events; make your handlers idempotent on the event ID.","**Skip:** processing webhook payloads synchronously in the HTTP handler; it risks timeouts, duplicates on failure, and cascading slowdowns."],"readTime":"5 min read","wordCount":1056},"related":[{"slug":"webhook-retry-and-idempotency-design-2026","title":"Webhook Retry and Idempotency Design in 2026","excerpt":"Webhooks are delivered at-least-once, which means every consumer must handle retries and duplicates safely. This guide covers event ID dedup, fast acknowledgment, and idempotent handler design for 2026.","category":"code","date":"August 10, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":304},{"slug":"webhooks-vs-polling-2026","title":"Webhooks vs polling in 2026: which push model wins?","excerpt":"Choosing between webhooks and polling shapes your app's latency, cost, and complexity. Here is the honest 2026 tradeoff guide with real numbers.","category":"code","date":"March 3, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":307},{"slug":"what-is-a-webhook-2026","title":"What is a webhook in 2026? Event-driven notifications explained","excerpt":"A webhook is an automated message a service sends to your app the moment something happens. This guide explains how webhooks work, how they differ from APIs, and how to use them safely in 2026.","category":"code","date":"May 7, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":240}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"vector-embeddings-explained-2026","title":"Vector embeddings explained in 2026: the practical guide for engineers","excerpt":"Vector embeddings turn text, images, and code into numbers that capture meaning. In 2026 they underpin semantic search, RAG, and recommendations — here is how they actually work and when to use them.","category":"ai","date":"April 21, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":198},"older":{"slug":"webpack-vs-vite-2026","title":"webpack vs Vite in 2026: which bundler should you choose?","excerpt":"Vite has become the default build tool for most new JavaScript projects, but webpack still powers the majority of production apps. Here is the honest 2026 breakdown.","category":"code","date":"April 21, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":322}}