Security
Learn how to secure your webhook endpoints and validate webhook signatures.
Signature Verification
Every webhook request includes an X-Zellify-Signature
header containing an HMAC SHA-256 hash of the raw request body computed using your webhook secret.
Always validate webhook signatures before processing any webhook data.
JavaScript / TypeScript (Node.js)
import crypto from 'crypto';
import express from 'express';
function verifyWebhookSignature(rawBody: Buffer, signature: string, secret: string): boolean {
const computed = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(computed, 'hex');
const b = Buffer.from(signature || '', 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
// Capture raw body for signature verification
app.use(express.json({
verify: (req: any, _res, buf) => { req.rawBody = buf; }
}));
app.post('/webhooks', (req: any, res) => {
const signature = req.header('X-Zellify-Signature') || '';
const isValid = verifyWebhookSignature(req.rawBody, signature, process.env.ZELLIFY_WEBHOOK_SECRET || '');
if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
// Process the webhook...
res.status(200).json({ received: true });
});
Python (Flask)
import hmac
import hashlib
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(secret.encode('utf-8'), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, signature or '')
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Zellify-Signature', '')
payload = request.get_data() # raw bytes
secret = os.environ.get('ZELLIFY_WEBHOOK_SECRET', '')
if not verify_webhook_signature(payload, signature, secret):
return jsonify({'error': 'Invalid signature'}), 401
# Process the webhook...
return jsonify({'received': True}), 200
PHP
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_ZELLIFY_SIGNATURE'] ?? '';
$secret = getenv('ZELLIFY_WEBHOOK_SECRET') ?: '';
$computed = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($computed, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
http_response_code(200);
echo json_encode(['received' => true]);
Best practices:
Validate the signature using the raw request body (not parsed JSON)
Use timing-safe comparison (
timingSafeEqual
,compare_digest
,hash_equals
)Keep your secret in environment variables and rotate periodically
Last updated