Delivery guarantees

Webhooks are at-least-once delivery by design. Your consumers must be idempotent — receiving the same event twice must produce the same result as receiving it once. Use the event ID as an idempotency key.

Signature verification

Sign every webhook payload with HMAC-SHA256 using a shared secret. Include the timestamp in the signed payload to prevent replay attacks.

// Publisher: sign the payload
const signature = createHmac("sha256", secret)
  .update(`${timestamp}.${JSON.stringify(payload)}`)
  .digest("hex");

// Consumer: verify before processing
const expected = createHmac("sha256", secret)
  .update(`${timestamp}.${body}`)
  .digest("hex");
if (!timingSafeEqual(Buffer.from(received), Buffer.from(expected)))
  throw new Error("Invalid signature");

Retry strategy

Retry on non-2xx responses. Exponential backoff: 1 min, 5 min, 30 min, 2 hr, 8 hr. Stop after 5 attempts and mark the delivery as failed. Alert the webhook owner.

Consumer best practices

  • Acknowledge immediately (return 200) — process asynchronously in a background job.
  • Check the timestamp — reject events older than 5 minutes (replay attack prevention).
  • Filter event types — subscribe only to the events you need.