Webhook Best Practices: A Checklist for Senders and Receivers (2026)

Webhook best practices for senders and receivers: Standard Webhooks signing, retries with backoff, endpoint isolation, 410 handling, replay; verify, ack fast, dedupe.

Webhook best practices come down to two short lists. Senders should sign every request with a per-endpoint secret (ideally the Standard Webhooks scheme), give each event a stable ID, retry with exponential backoff and Retry-After, isolate each endpoint, time out quickly, stop on 410 Gone and keep a delivery log they can replay. Receivers should verify the signature against the raw body, respond 2xx fast, queue the real work and deduplicate on the event ID.

Most webhook advice is written for one side. This checklist covers both, because the failures are shared: a sender with no backoff takes down a receiver that is already struggling, and a receiver that does slow work inline causes the timeouts that make the sender retry. If you send webhooks to your customers, the first half is your spec. If you consume them, the second half is.

The checklist at a glance

PracticeSideWhy it matters
Sign with HMAC-SHA256 per endpoint (Standard Webhooks)SenderReceivers can prove the request is yours and untampered
Stable webhook-id per event, fresh timestamp per attemptSenderReceivers dedupe retries; old captures can't be replayed
Exponential backoff with a deadline, honour Retry-AfterSenderSurvives outages without hammering a struggling endpoint
Per-endpoint queues, rate limits and concurrency capsSenderOne slow customer never delays the others
5-15 second timeouts, no redirectsSenderWorkers stay free; signatures and SSRF rules hold
Stop on 410 Gone, auto-disable dead endpointsSenderNo retry traffic to URLs that will never answer
Typed, documented, additive event typesSenderCustomers subscribe to what they need; nothing breaks on change
Small payloads, no secretsSenderFewer timeouts, nothing sensitive sitting in logs
Block private and loopback destinationsSenderYour webhook system is not an SSRF proxy
Log every attempt, support retry and replaySenderSupport can answer "did you send it?" in seconds
Rotate secrets with an overlap windowSenderCustomers switch keys with zero dropped deliveries
Publish through an outbox with idempotency keysSenderEvents survive crashes between commit and send
Verify the signature on the raw body, check the timestampReceiverRejects forged and replayed requests
Respond 2xx within a few seconds, queue the workReceiverAvoids timeouts and duplicate deliveries
Deduplicate on the event ID with a unique constraintReceiverAt-least-once delivery becomes effectively once
Don't assume orderingReceiverRetries and parallel lanes reorder events
Reconcile against the sender's APIReceiverCatches anything that was never delivered

Best practices for webhook senders

1. Sign every delivery, with a separate secret per endpoint

An unsigned webhook is an unauthenticated POST to a public URL: anyone who learns the URL can forge events. Sign each request with HMAC-SHA256 and give every endpoint its own secret, so one leaked key compromises one integration, not all of them.

Don't invent a scheme. Standard Webhooks is an open spec with libraries in most languages:

webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1761382800
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

The signature is base64(HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{raw body}")), and the secret is a whsec_ prefixed base64 key. Customers who already verify another Standard Webhooks provider can reuse their code. You can generate test headers with the Standard Webhooks signature generator and check them with the verifier.

2. Give each event a stable ID and a fresh timestamp per attempt

The event ID (webhook-id) must be identical across every retry and every manual replay; it is what receivers deduplicate on. The timestamp should be the time of this attempt, re-signed each time, so receivers can reject anything older than a few minutes and a captured request can't be replayed next week.

3. Retry with exponential backoff, a deadline and Retry-After

Failures are normal: deploys, cold starts, a database failover. Retry any non-2xx response, timeout or connection error, with gaps that grow quickly and a hard stop:

AttemptDelay before it (Webhook Relay Outbound Gateway schedule)
1none, sent immediately
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours, and never later than 48 hours after publishing

Stripe retries for up to three days; one to three days is the usual range. Add a little jitter so a recovering endpoint isn't hit by a synchronized wave. When a receiver answers 429 Too Many Requests or 503 Service Unavailable with a Retry-After header, wait at least that long (cap it, for example at one hour). For the receiving side of this story, see webhook retries and idempotency.

4. Isolate every endpoint

The classic homegrown bug: one global queue and a pool of workers. A customer whose endpoint takes 30 seconds to time out ties up workers, and every other customer's webhooks queue behind it. Give each endpoint its own lane: its own rate limit (for example 50 requests per second), its own concurrency cap and its own retry schedule. A slow or dead endpoint should only ever delay itself.

5. Use short timeouts and don't follow redirects

A 5-15 second timeout is plenty for a receiver that acknowledges and queues. Longer timeouts just let slow receivers consume your worker capacity. Let customers raise it per endpoint if they really need to (up to 60 seconds is a sensible ceiling).

Treat 3xx responses as failures. Following a redirect can send a signed payload to a host the customer never registered, and it is a classic way around SSRF checks.

6. Stop on 410 Gone and auto-disable dead endpoints

410 Gone is the receiver telling you to stop: disable the endpoint and don't retry. For endpoints that just keep failing, track health per endpoint (active → failing after a run of failures) and disable automatically after a long, consistent outage, such as 100 consecutive failures spanning at least 12 hours. Tell the customer when this happens; don't make them find out from missing data.

7. Design event types like an API

  • Name them resource.action (invoice.paid, subscription.canceled) and publish a catalog with an example payload for each.
  • Let endpoints subscribe to specific event types instead of receiving everything.
  • Treat the payload as a public API: add fields freely, never remove or rename them without a new version or a new event type.
  • Deprecate rather than delete, so nothing new is published to an event type customers still depend on.

8. Keep payloads small and free of secrets

Large bodies cause timeouts and fill logs. Set a size limit (256 KiB is generous) and for big objects send an ID plus a link the receiver can fetch with its API key (a "thin" payload). Never put passwords, tokens or full card numbers in a webhook: payloads end up in delivery logs, receiver logs and support tickets.

9. Block private and internal destinations

Customers type URLs into your product, and your servers then make requests to them. Without checks, http://169.254.169.254/ or https://10.0.0.5/admin turns your webhook system into an SSRF proxy into your own network. Require HTTPS, reject hosts that resolve to private, loopback or link-local addresses at registration, and check again when you connect, because DNS can change after registration.

10. Log every attempt and make replay a button

When a customer says "we never got the invoice.paid for inv_123," support needs to answer in seconds: when each attempt was made, what status and body came back, and whether it is still retrying. Store every attempt with its request headers and (truncated) response for at least 30 days. Then make recovery cheap:

  • Retry one delivery to one endpoint.
  • Recover everything that exhausted its retries since a point in time (the customer's outage started Tuesday 14:00).
  • Replay missing messages that were skipped while an endpoint was paused.

Every copy must keep its original event ID so deduplicating receivers stay correct.

11. Rotate secrets with an overlap window

Customers will need to rotate keys after a leak or an employee leaving. If rotation swaps the key instantly, every delivery fails until they deploy the new secret. Instead, sign with both the new and old secrets for a window (24 hours works), sending two v1, signatures in the header. Standard Webhooks receivers accept any matching signature, so the switch is seamless.

12. Publish durably, with idempotency keys

The first place a webhook gets lost is inside your own service: the order commits, the process crashes, the send never happens. Write the event to an outbox table in the same database transaction as the business change, and publish from a worker. Make the publish call idempotent (an Idempotency-Key derived from the event), so a worker that retries after a timeout can't create a second message. The webhook system design post covers this architecture end to end.

Best practices for webhook receivers

1. Verify the signature against the raw body

Compute the HMAC over the exact bytes received, not a re-serialized JSON object, compare in constant time and reject timestamps more than about five minutes from now. Accept any of several signatures in the header so secret rotation doesn't break you. Provider-specific details and code are in how to verify a webhook signature; broader hardening is in webhook security.

2. Acknowledge fast, work later

Return a 2xx as soon as the event is durably stored, ideally in well under a second and always within the sender's timeout. Charging cards, sending email and calling other APIs belong in a background job. Slow handlers cause timeouts, and timeouts cause duplicates.

3. Deduplicate on the event ID

Delivery is at-least-once everywhere. Insert the event ID into a table with a unique constraint before acting, in the same transaction as the side effect; if the insert conflicts, return 200 and do nothing.

4. Don't depend on ordering

Retries, parallel delivery lanes and replays all reorder events. Use timestamps or version numbers in the payload to ignore stale updates, or treat the webhook as a signal and fetch the current state from the sender's API.

5. Use status codes deliberately

2xx means done. 401 for a failed signature. 429 or 503 with Retry-After when you need the sender to back off. 410 only when you want the sender to stop delivering to that URL permanently. Avoid returning 500 for events you deliberately ignore; that just schedules retries.

6. Reconcile periodically

Even with retries, an endpoint can be down longer than the sender's retry window. A nightly job that lists recent objects from the sender's API and compares them with what you processed catches the gaps.

Or don't build the sender side

The receiver checklist is a few dozen lines of code. The sender checklist is a system: a durable queue, a retry scheduler, a signer, per-endpoint rate limiting, a health state machine, SSRF protection, a delivery log with retention and a recovery pipeline, operated for years.

If you would rather not own that, Webhook Relay's Outbound Gateway is webhooks as a service built to this checklist: you publish an event once through the REST API, the TypeScript or Go SDK or MCP, and it delivers Standard Webhooks signed requests to each customer's endpoints with the retry schedule above, per-endpoint lanes (50 requests per second by default), 410 handling, optional auto-disable, 24-hour secret rotation overlap and 35 days of per-attempt history with retry, recover and replay. It starts at $9.99/month on the Basic plan with no per-message fees. It does not have an embeddable customer portal, FIFO ordering or non-HTTP destinations, so check those against your requirements; the webhooks as a service comparison covers the alternatives.

To try it, follow send webhooks from Node.js / TypeScript or from a Go service, or start with the Outbound Gateway docs.

Frequently asked questions

What are the most important webhook best practices?

For senders: sign every request with a per-endpoint secret using the Standard Webhooks scheme, give each event a stable ID, retry failures with exponential backoff for one to three days, honour Retry-After, isolate each endpoint so one slow receiver cannot delay others, stop on 410 Gone and keep a delivery log you can replay. For receivers: verify the signature against the raw body, respond with a 2xx within a few seconds, queue the real work and deduplicate on the event ID.

How long should a webhook sender retry a failed delivery?

Long enough to outlast a normal outage, usually one to three days, with exponentially increasing gaps and a hard deadline. Stripe retries for up to three days. Webhook Relay's Outbound Gateway retries after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours, up to eight attempts within 48 hours. Keep the failed deliveries afterwards so they can be replayed once the receiver is fixed.

How should a webhook endpoint respond?

Verify the signature, store the event or put it on a queue, and return any 2xx status within a few seconds, before doing slow work such as calling other APIs. Return 401 for a bad signature, 429 or 503 with a Retry-After header when you are overloaded, and 410 Gone only when you want the sender to stop delivering to that URL for good.

What is the Standard Webhooks specification?

Standard Webhooks is an open specification for signing and delivering webhooks. Each request carries webhook-id, webhook-timestamp and webhook-signature headers, and the signature is an HMAC-SHA256 over the ID, timestamp and raw body, keyed with a whsec_ prefixed secret. Because many providers use it, receivers can verify any compliant sender with one library.

Should I build my own webhook sending system?

Build it if webhooks are core to your product and you need things hosted services lack, such as strict ordering, non-HTTP destinations or on-premises deployment. Otherwise a webhooks as a service platform is usually cheaper than the engineering time: the sender checklist is a queue, a retry scheduler, a signer, rate limiting, health tracking and a delivery log to run for years. Webhook Relay's Outbound Gateway covers it from $9.99 per month with no per-message fees.