How It Works
Webhooks let you receive automatic notifications when events occur in Veridox, such as a file analysis completing.
Create a webhook configuration
An organisation owner creates a webhook configuration via the API, providing a destination URL where you want to receive events.
Store your signing secret
Veridox provisions a signing secret (format: whsec_{32-char-alphanumeric}) and returns it once in the creation response. After that, only a prefix is shown for identification.The secret is only shown once at creation time. Store it securely — you cannot retrieve it again.
Receive events
When events occur, Veridox sends a POST request to your URL with:
- A JSON payload in the body
- An
x-vdx-signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret
Verify and process
You verify the signature on your server to confirm the request genuinely came from Veridox and hasn’t been tampered with, then process the event.
Event Types
| Event type | Description |
|---|
file.enrichment.success | File analysis completed successfully |
file.enrichment.failed | File analysis encountered an error |
Payload Structure
When a file analysis completes successfully, Veridox delivers a payload like this:
{
"event_type": "file.enrichment.success",
"file_id": "019e2a1b-7c3d-7f00-8a1b-2c3d4e5f6a7b",
"case_id": "019e2a1b-5b2c-7e00-9d8c-7b6a5f4e3d2c",
"started_at": "2026-02-17T10:30:00.000Z",
"completed_at": "2026-02-17T10:30:42.000Z",
"version": "2.0.0",
"file_info": {
"md5": "d41d8cd98f00b204e9800998ecf8427e",
"mimetype": "application/pdf"
},
"results": {
"overall_risk_score": "high",
"summary": "Multiple indicators of document manipulation detected...",
"findings": ["..."],
"observations": ["..."],
"risk_assessments": ["..."],
"suggested_actions": ["..."],
"audit_trail": ["..."]
},
"modules": [
{
"module_id": "metadata_analysis",
"module_name": "Metadata Analysis",
"findings": ["..."],
"grounding_sources": ["..."]
}
],
"artifacts": {
"report_pdf": "reports/019e2a1b-7c3d-7f00.../report.pdf",
"report_log": "reports/019e2a1b-7c3d-7f00.../report.log"
},
"analysis_metadata": {
"...": "..."
}
}
For file.enrichment.failed events, the payload includes an error field instead of results.
Signature Verification
The signature is computed as:
HMAC-SHA256(raw_request_body, your_webhook_secret) → hex string
You compare the x-vdx-signature header value against your own computed HMAC. Always use a timing-safe comparison to prevent timing attacks.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
const WEBHOOK_SECRET = "whsec_your_secret_here";
function verifySignature(body, signature) {
if (!signature) return false;
const expected = createHmac("sha256", WEBHOOK_SECRET)
.update(body)
.digest("hex");
const sigBuf = Buffer.from(signature, "utf8");
const expBuf = Buffer.from(expected, "utf8");
if (sigBuf.length !== expBuf.length) return false;
return timingSafeEqual(sigBuf, expBuf);
}
createServer(async (req, res) => {
if (req.method !== "POST") {
res.writeHead(404).end();
return;
}
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const body = Buffer.concat(chunks);
if (!verifySignature(body, req.headers["x-vdx-signature"])) {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Invalid signature" }));
return;
}
const event = JSON.parse(body.toString("utf8"));
console.log("Verified webhook event:", event);
// Process the event...
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
}).listen(3000, () => console.log("Webhook receiver on :3000"));
import hmac
import hashlib
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
WEBHOOK_SECRET = "whsec_your_secret_here"
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
signature = self.headers.get("x-vdx-signature", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
self.send_response(401)
self.end_headers()
self.wfile.write(b'{"error": "Invalid signature"}')
return
event = json.loads(body)
print("Verified webhook event:", event)
# Process the event...
self.send_response(200)
self.end_headers()
self.wfile.write(b'{"ok": true}')
HTTPServer(("", 3000), WebhookHandler).serve_forever()
Best Practices
Store secrets securely — the signing secret is shown once at creation time and cannot be retrieved again
Always verify signatures — validate every incoming request before processing the payload
Use timing-safe comparison — use timingSafeEqual (Node.js) or hmac.compare_digest (Python) to prevent timing attacks
Return 2xx quickly — acknowledge receipt immediately and do heavy processing asynchronously
Only one active webhook configuration is allowed per organisation at a time. Creating a new configuration deactivates the previous one.