Signature verification
The real HMAC-SHA256 scheme VUZ uses — verified against the delivery code, not assumed.
This is not the Stripe-style t=,v1= scheme you may have seen described elsewhere.
VUZ's actual implementation signs the raw payload only — the timestamp header is separate
and unsigned. Use the exact recipe below.
The headers VUZ sends
Content-Type: application/json
User-Agent: VUZ-Webhook/1.0
X-Vuz-Signature: 5f3d8a1c9b... (hex-encoded HMAC-SHA256)
X-Vuz-Timestamp: 1751371260 (unix seconds — NOT part of the signature)
X-Vuz-Delivery-Id: 9c2f6b2e-2e1e-4b7e-9f1e-1e2e3e4e5e6e
X-Vuz-Event: document.finalizedHow the signature is computed
signature = hex( HMAC_SHA256( endpoint_secret, JSON.stringify(payload) ) )endpoint_secret is the random secret generated when you created the webhook endpoint
(POST /api/v1/webhooks). The HMAC input is only the JSON-serialized payload body —
X-Vuz-Timestamp is not mixed into the signature, so you don't need it to verify (it's
informational / useful for your own replay-window policy, not part of the cryptographic
check).
Verify against the raw request body bytes, not a value you re-serialize yourself.
JSON.stringify(JSON.parse(body)) can reorder keys or change number/whitespace formatting
and silently break signature comparison. Capture the raw body before your framework parses
it as JSON.
Verify it — Node.js
import crypto from 'crypto';
import express from 'express';
const app = express();
// IMPORTANT: capture the raw body BEFORE JSON parsing
app.post(
'/webhooks/vuz',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-vuz-signature'];
const rawBody = req.body; // Buffer, exact bytes VUZ sent
const expected = crypto
.createHmac('sha256', process.env.VUZ_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const valid =
signature &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(rawBody.toString('utf8'));
console.log('Verified event:', event.event ?? req.headers['x-vuz-event']);
res.status(200).send('ok');
},
);Verify it — PHP
<?php
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_VUZ_SIGNATURE'] ?? '';
$secret = getenv('VUZ_WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($rawBody, true);
http_response_code(200);
echo 'ok';Verify it — Python
import hashlib
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['VUZ_WEBHOOK_SECRET']
@app.post('/webhooks/vuz')
def handle_webhook():
raw_body = request.get_data() # exact bytes VUZ sent
signature = request.headers.get('X-Vuz-Signature', '')
expected = hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, 'invalid signature')
event = request.get_json()
print('Verified event:', event.get('event'))
return 'ok', 200Test against a real signature
POST /api/v1/webhooks/{id}/test fires a real, correctly signed delivery at your endpoint
— use it to confirm your verification code against a live signature before going to
production, rather than hand-computing a fixture.

