Webhook Security
Every webhook delivery from Orbit includes an HMAC-SHA256 signature in theX-Devotel-Signature header. Always verify this signature to ensure the request genuinely came from Orbit and hasn’t been tampered with.
Signature Format
TheX-Devotel-Signature header is Stripe-style, not a bare hex string:
t=<unix>— Unix timestamp (seconds) when Orbit signed the payload. Use this for replay protection.v1=<hex>— HMAC-SHA256 of the string<t>.<raw_body>keyed by your webhook signing secret. Hex-encoded.
v1=<hex> values in the same header (new secret first, previous secret second) so a verifier configured with either secret continues to validate:
,, parse each key=value pair, and accept the request if any v1 matches your computed HMAC.
How Signature Verification Works
- Parse the
X-Devotel-Signatureheader into a timestamptand one or morev1candidate signatures. - Reject the request if
tis older than 5 minutes (replay protection). - Compute
expected = HMAC_SHA256(secret, "<t>.<raw_body>")as hex. - Compare
expectedagainst eachv1candidate using a timing-safe comparison.
Verification Examples
Node.js
Python
Go
Security Best Practices
- Always verify signatures — never process webhooks without checking the signature
- Use timing-safe comparison — prevents timing attacks (
crypto.timingSafeEqualin Node.js,hmac.compare_digestin Python) - Use HTTPS — your webhook endpoint must use HTTPS to protect payloads in transit
- Rotate secrets — rotate your webhook signing secret periodically in Settings > Webhooks
- Idempotency — use the event
idfield to deduplicate, since Orbit guarantees at-least-once delivery - Respond quickly — return a
2xxwithin 30 seconds; process heavy work asynchronously - IP allowlisting — optionally restrict incoming webhooks to Orbit’s IP ranges (available in the dashboard under Settings > Security)
Signing Secret
Your webhook signing secret (prefixed withwhsec_) is generated when you create a webhook. The cleartext secret is returned only once — in the response to POST /api/v1/webhooks (endpoint creation) or POST /api/v1/webhooks/{id}/rotate-secret (rotation). Store it securely at that moment.
GET /api/v1/webhooks/{id} does not return a usable secret — it returns a masked preview (whsec_********...<last4>) for identification only. Do not copy that masked value into your verification logic; the HMAC will never match and signature checks will silently fail.
If you lose the secret, rotate to obtain a new one: