Webhook Verification
Webhook Verification
Section titled “Webhook Verification”Every webhook delivery includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify signatures to ensure webhooks are genuinely from our platform.
Signature Format
Section titled “Signature Format”sha256=<hex-encoded HMAC-SHA256>The signature is computed over the raw request body using your webhook’s secret as the key.
Verification Algorithm
Section titled “Verification Algorithm”- Get the
X-Webhook-Signatureheader value - Compute HMAC-SHA256 of the raw request body using your webhook secret
- Compare using a timing-safe comparison
Code Examples
Section titled “Code Examples”Node.js
Section titled “Node.js”const crypto = require('crypto');
function verifyWebhook(rawBody, signature, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret) .update(rawBody) .digest('hex');
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}
// Express middleware exampleapp.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-webhook-signature']; const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) { return res.status(401).send('Invalid signature'); }
const event = JSON.parse(req.body); console.log('Received event:', event.event);
// Process the event...
res.status(200).send('OK');});Python
Section titled “Python”import hmacimport hashlib
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask examplefrom flask import Flask, request
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])def webhook(): signature = request.headers.get('X-Webhook-Signature') secret = os.environ['WEBHOOK_SECRET']
if not verify_webhook(request.data, signature, secret): return 'Invalid signature', 401
event = request.json print(f'Received: {event["event"]}')
return 'OK', 200function verifyWebhook($rawBody, $signature, $secret) { $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret); return hash_equals($expected, $signature);}
$rawBody = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];$secret = getenv('WEBHOOK_SECRET');
if (!verifyWebhook($rawBody, $signature, $secret)) { http_response_code(401); exit('Invalid signature');}
$event = json_decode($rawBody, true);Security Best Practices
Section titled “Security Best Practices”- Always verify signatures before processing events
- Use timing-safe comparison functions (not
==or===) - Store the webhook
secretsecurely (environment variable) - Respond with
2xxquickly, then process asynchronously - Log all webhook deliveries for debugging