Skip to content

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.

sha256=<hex-encoded HMAC-SHA256>

The signature is computed over the raw request body using your webhook’s secret as the key.

  1. Get the X-Webhook-Signature header value
  2. Compute HMAC-SHA256 of the raw request body using your webhook secret
  3. Compare using a timing-safe comparison
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 example
app.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');
});
import hmac
import 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 example
from 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', 200
function 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);
  • Always verify signatures before processing events
  • Use timing-safe comparison functions (not == or ===)
  • Store the webhook secret securely (environment variable)
  • Respond with 2xx quickly, then process asynchronously
  • Log all webhook deliveries for debugging