Webhooks that actually reach you: HMAC, retries and idempotency

The three things every payments webhook integration gets wrong. Here is the contract Baynoy ships and why.

Developers·2026-04-01·6 min read
Webhooks that actually reach you: HMAC, retries and idempotency

Every payments webhook needs three guarantees: authenticity (the request really came from us), at-least-once delivery (no events silently lost), and idempotency (re-deliveries do not double-charge your DB).

Authenticity: every Baynoy webhook ships a Baynoy-Signature header — t=<ms>,v1=<hex> (v1 = HMAC-SHA256 of timestamp + '.' + body) with your endpoint's secret. Reject if the timestamp is older than 5 minutes (replay defense) or the signature does not constant-time compare. We follow a standard HMAC-SHA256 signature format, so the signature-verification middleware you already use drops in unchanged.

At-least-once: we retry with exponential backoff at 1m, 5m, 30m, 2h, 12h, 24h — capped at six attempts. Each attempt writes a row to baynoy_webhook_deliveries with the response status. Your replay-from-UI button in the dashboard hits the same delivery pipeline; no special path.

Idempotency: every event carries an event.id (UUIDv7 — sortable, monotonically increasing). Persist it on first receipt and reject duplicates at the DB level with a unique constraint. We will redeliver. The constraint is your safety net.

Three lines of middleware and you have a webhook handler that survives a 6-hour outage without losing a single payment notification.

Want the deep dive when it ships? Sign up below.