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.