Why this matters
Webhook URLs are often leaked — in logs, through public DNS scans, via employees’ HTTP client history. If you accept unsigned POSTs to your webhook endpoint as authoritative, you’re one URL-leak away from an attacker injecting faketransfer.completed events and triggering whatever downstream logic you’ve wired up.
What to check
For every incoming webhook:- Header present — reject anything without
X-Sly-Signature - Timestamp fresh — reject
tolder than 5 minutes (replay protection) - Signature matches — HMAC-SHA256 of
{t}.{raw_body}using your webhook secret - Constant-time compare — never
==, alwaystimingSafeEqual/ equivalent
Where to get the secret
Shown once when you create the webhook:Rotation strategies
Overlap rotation (recommended):overlap_seconds, Sly accepts either old or new secret. Deploy the new secret, verify traffic, then let the overlap expire.
Immediate rotation:
Example (Node, Express)
express.raw is critical — verifying against a re-serialized JSON body fails because whitespace differs.
Failure modes to watch for
- Body parsed before verification — body is re-serialized, signature mismatches. Use a raw-body middleware for the webhook route.
- Proxy strips headers — many WAFs or CDNs strip
X-*headers. AllowlistX-Sly-*. - Clock drift — server clocks more than 5 minutes off kill every verification. NTP.
==comparison — enables timing attacks. Use constant-time comparison.- Secret in source control — rotate immediately if committed.
Testing
Fire a test delivery to any endpoint:webhook.test event to the endpoint with a valid signature. Use this to validate your verification code before going live.