Webhooks guide

Authenticate events before side effects.

Verify signatures against the raw body, reject replay, acknowledge safely, and process duplicate delivery idempotently.

01 / Verify

Use the raw body.

Verify the provider signature and timestamp against the exact raw request body before parsing or mutating state. Each webhook source has an isolated secret and verification adapter; a session cookie or source IP is never treated as proof of authenticity.

Verification order
const event = verifySignature({
  rawBody,
  signature: request.headers["webhook-signature"],
  timestamp: request.headers["webhook-timestamp"],
  secret: process.env.WEBHOOK_SIGNING_SECRET,
});

02 / Replay

Make duplicate delivery harmless.

Reject timestamps outside the configured tolerance and claim the provider event ID in a unique store before side effects. Duplicate valid deliveries return a successful acknowledgement after confirming the original result.

  • Use a unique provider plus event-ID constraint.
  • Keep signature failures generic and free of secret material.
  • Store normalized event state, not raw credential-bearing payloads in logs.

03 / Delivery

Durable first, asynchronous second.

Acknowledge only after durable acceptance. Move slow work to a queue, return a 2xx response quickly, retry with bounded exponential backoff, and route exhausted attempts to a dead-letter queue with alarms and replay tooling.

Build against the contract

Move from guidance to implementation.