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?
What made this section helpful for you?
What made this section unhelpful for you?
On this page
- Webhook Signature Verification