Reference
Webhook security
How billing webhooks are verified, deduplicated and stored.
- Signature first. Every billing webhook is verified against your signing secret over the raw request body, before anything is parsed. An unsigned or mis-signed request gets a 400 and nothing is stored.
- Replay window. Schemes with a timestamp (Stripe, Paddle, Polar and Dodo's Standard Webhooks) are refused when the timestamp is more than five minutes off.
- Idempotent. Each event is stored once per program, provider and event id. Re-deliveries and retries are no-ops, so a provider retrying for an hour can't double a commission.
- Order-independent. A payment that arrives before the signup that names its affiliate is held and attributed the moment the link is made.
- Secrets at rest (webhook secrets, Stripe restricted keys) are encrypted with AES-256-GCM. API keys are stored as a SHA-256 hash and shown once.
- One URL per connection. The webhook URL contains your connection's id:
/api/webhooks/<provider>/<connection-id>. Disconnecting a provider makes the old URL stop accepting events.
| Provider | Header | Scheme |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 of timestamp.body |
| Polar | webhook-signature | Standard Webhooks |
| Lemon Squeezy | X-Signature | HMAC-SHA256 hex of the body |
| Paddle | Paddle-Signature | HMAC-SHA256 of ts:body |
| Creem | creem-signature | HMAC-SHA256 hex of the body |
| Dodo Payments | webhook-signature | Standard Webhooks |