Webhook Security
Webhook Security
All webhook payloads are signed using HMAC-SHA256 to verify authenticity.
Headers
| Header | Description |
| X-Claudio-Signature | HMAC-SHA256 signature (hex-encoded) |
| X-Claudio-Timestamp | Unix timestamp of the request |
Verifying Signatures
-
Retrieve the raw JSON request body
-
Compute HMAC-SHA256(body, your_webhook_secret)
-
Compare the result with the X-Claudio-Signature header
Important: Use a constant-time comparison function (e.g., hash_equals() in PHP, hmac.compare_digest() in Python) to prevent timing attacks.
Example (PHP)
$payload = file_get_contents('php://input');
$signature = hash_hmac('sha256', $payload, $webhookSecret);
if (!hash_equals($signature, $_SERVER['HTTP_X_CLAUDIO_SIGNATURE'])) {
http_response_code(401);
exit('Invalid signature');
}
Example (Python)
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
# In your webhook handler
if not verify_webhook(request.body, request.headers['X-Claudio-Signature'], webhook_secret):
return Response(status=401)
Example (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
if (!verifyWebhook(req.body, req.headers['x-claudio-signature'], webhookSecret)) {
return res.status(401).send('Invalid signature');
}