Webhook Signatures
Every webhook delivery is signed with your webhook's secret so you can verify it came from Vidocu.
Signature format
The signature is sent in the X-Vidocu-Signature header:
X-Vidocu-Signature: sha256=a1b2c3d4e5f6...
Verification algorithm
- Get the timestamp from
X-Vidocu-Timestamp - Get the raw request body as a string
- Concatenate:
{timestamp}.{body} - Compute HMAC SHA-256 using your webhook secret as the key
- Compare with the signature (after removing the
sha256=prefix)
Code examples
Node.js
import crypto from "crypto";
function verifyWebhook(
body: string,
signature: string,
timestamp: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
const received = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
Python
import hmac
import hashlib
def verify_webhook(body: str, signature: str, timestamp: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
f"{timestamp}.{body}".encode(),
hashlib.sha256
).hexdigest()
received = signature.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
Express middleware example
import express from "express";
import crypto from "crypto";
const app = express();
app.post(
"/webhooks/vidocu",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-vidocu-signature"] as string;
const timestamp = req.headers["x-vidocu-timestamp"] as string;
const body = req.body.toString();
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(`${timestamp}.${body}`)
.digest("hex");
const received = signature.replace("sha256=", "");
if (
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(body);
console.log("Received event:", event.event);
// Process the event...
res.status(200).send("OK");
}
);
Security tips
- Always verify signatures before processing events
- Use
timingSafeEqual(or equivalent) to prevent timing attacks - Reject requests with timestamps older than 5 minutes to prevent replay attacks
- Use HTTPS for your webhook endpoint