Security & Verification
Every webhook delivery includes cryptographic headers so you can verify that the request came from Staffify and has not been tampered with.
Request Headers
| Header | Description |
|---|---|
| X-Webhook-Signature | HMAC-SHA256 signature in the format sha256=<hex> |
| X-Webhook-Timestamp | Unix timestamp (seconds) when the webhook was sent |
| X-Webhook-Event | The event type (e.g., ticket.created) |
| X-Webhook-Delivery | Unique delivery ID for deduplication and tracking |
| User-Agent | Staffify-Webhooks/1.0 |
| Content-Type | application/json |
Verifying Signatures
The signature is computed as: HMAC-SHA256(secret, timestamp + "." + raw_body)
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const body = JSON.stringify(req.body);
// Reject if timestamp is older than 5 minutes (replay protection)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
// Compute expected signature
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + body)
.digest('hex');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Python
import hmac
import hashlib
import time
import PrevNextNav from '../../_components/PrevNextNav';
def verify_webhook(headers, body, secret):
signature = headers.get('X-Webhook-Signature', '')
timestamp = headers.get('X-Webhook-Timestamp', '')
# Reject if timestamp is older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{body}"
expected = 'sha256=' + hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Important
- Always verify the signature before processing any webhook payload.
- Use constant-time comparison to prevent timing attacks.
- Check the timestamp to reject replayed requests older than 5 minutes.