Webhook Security
Signing secrets, timestamp replay protection, IP allowlisting, and secret rotation.
Signing secrets
Each webhook subscription has its own signing secret, issued once at creation. The secret is used to compute an HMAC-SHA256 signature over every delivery payload.
- Store the secret in your secrets manager, never in source control.
- If lost, rotate the subscription and create a new one.
- Each subscription has an independent secret; compromising one does not affect others.
Timestamp replay protection
Every delivery includes an X-OuiPay-Timestamp header (Unix seconds). Your
verification logic should reject timestamps older than 5 minutes:
if abs(now - timestamp) > 300 seconds → rejectThis prevents an attacker who captures a valid signed payload from replaying it later.
Event deduplication
Deliveries are at-least-once. The event_key field uniquely identifies each
event. Before performing any side-effect (credit, fulfill, mark-paid), check
whether you have already processed that event_key.
A payment.completed replay that credits the customer twice is a financial
loss. event_key is the guard.
HTTPS only
OuiPay delivers webhooks only to https:// endpoints. Plain HTTP endpoints
are rejected at subscription creation.
IP allowlisting
For defense-in-depth, you can restrict inbound webhooks to OuiPay's delivery IP ranges. Contact support for the current IP list. This is a supplementary measure: always verify signatures regardless of source IP.
Rotating a signing secret
If you suspect a signing secret has been compromised:
- Create a new webhook subscription with the same URL and events.
- Update your verification code with the new secret.
- Delete the old subscription.
During the transition, you may briefly receive deliveries from both
subscriptions. Your event_key deduplication handles this.
Common mistakes
| Mistake | Risk | Fix |
|---|---|---|
| Not verifying signatures | Attacker can forge events | Always verify before side-effects |
| Verifying parsed JSON instead of raw bytes | Signature mismatch | Verify against the raw request body |
| No timestamp check | Replay attacks | Reject timestamps older than 5 minutes |
No event_key deduplication | Double-crediting | Store and check processed event_key values |
| Slow handler (>10s) | Retries cause duplicates | Ack fast, process async |