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.