Webhook Signature Verification
Every webhook delivery includes an HMAC-SHA256 signature for authenticity verification. Always verify signatures before processing webhook payloads.
Signature Header
Plain text
Verification Steps
- Extract the signature from
X-CipherStream-Signatureheader (removesha256=prefix) - Get the raw request body (do not parse JSON first)
- Compute HMAC-SHA256 using your webhook secret
- Compare signatures using constant-time comparison
Reference Implementations
Python
Python
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# Usage
raw_body = request.body # Raw bytes, not parsed JSON
signature = request.headers.get('X-CipherStream-Signature')
if not verify_signature(raw_body, signature, your_webhook_secret):
return Response(status=401)Node.js
JavaScript
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// Usage
const rawBody = req.rawBody; // Raw string, not parsed
const signature = req.headers['x-cipherstream-signature'];
if (!verifySignature(rawBody, signature, yourWebhookSecret)) {
return res.status(401).send('Invalid signature');
}C# (.NET)
Plain text
Secret Rotation
CipherStream supports zero-downtime webhook secret rotation with dual-secret validation.
- Grace Period: When a secret is rotated, both the old and new secrets are valid for a configurable grace period (default: 7 days)
- Seamless Transition: Update your verification code to accept either secret during rotation
- Rotation Notification: You'll receive notification before rotation occurs
Dual-Secret Verification Example (Python)
Python
def verify_with_rotation(payload: bytes, signature: str,
current_secret: str, previous_secret: str = None) -> bool:
# Try current secret first
if verify_signature(payload, signature, current_secret):
return True
# Fall back to previous secret during rotation
if previous_secret and verify_signature(payload, signature, previous_secret):
return True
return FalseSecurity Best Practices
Practice | Description |
Constant-time comparison | Always use timing-safe comparison functions |
Verify before processing | Reject requests with invalid signatures immediately |
Use raw payload | Verify against raw bytes, not parsed/reformatted JSON |
Secure secret storage | Store webhook secrets in a secrets manager |
Monitor failures | Alert on repeated signature verification failures |
Retry Behaviour
Attempt | Delay | Total Wait |
1 | Immediate | 0s |
2 | 1 minute | 1m |
3 | 5 minutes | 6m |
4 | 15 minutes | 21m |
5 | 1 hour | 1h 21m |
6 | 2 hours | 3h 21m |
Webhooks are retried on 5xx errors and timeouts. Return 2xx to acknowledge successful receipt.
Was this section helpful?
On this page
- Webhook Signature Verification