2. Event payload
Every webhook request contains a JSON body with a consistent envelope:
{
"id": "evt_1234abcd-5678-90ef-...",
"type": "screening.completed",
"created": "2025-11-04T16:32:49.507Z",
"version": "1",
"data": {
// Event-specific payload
}
}
id – globally unique event ID (UUID). Use this for idempotency in your system.type – event name (e.g. screening.completed, webhook.test).created – ISO-8601 timestamp when the event was created.version – payload schema version (currently "1").data – event-specific body (for example, the screening job and result summary).
4. Verifying webhooks
You should verify every webhook request before you trust it. The basic algorithm is:
- Read the raw request body as a string.
- Read the
x-matchaudit-signature header. - Compute your own HMAC-SHA256 digest using your endpoint's signing secret and the raw body.
- Compare using a constant-time comparison. If they match, accept the webhook.
4.1 Node / TypeScript example (Express)
import crypto from "crypto";
import type { Request, Response } from "express";
const MATCHAUDIT_SECRET = process.env.MATCHAUDIT_WEBHOOK_SECRET!;
function timingSafeEqual(a: string, b: string): boolean {
const aBuf = Buffer.from(a, "utf8");
const bBuf = Buffer.from(b, "utf8");
if (aBuf.length !== bBuf.length) return false;
return crypto.timingSafeEqual(aBuf, bBuf);
}
export function handleMatchAuditWebhook(req: Request, res: Response) {
const rawBody = (req as any).rawBody?.toString("utf8") ?? JSON.stringify(req.body);
const sigHeader = req.header("x-matchaudit-signature") || "";
const expected = "sha256=" + crypto
.createHmac("sha256", MATCHAUDIT_SECRET)
.update(rawBody, "utf8")
.digest("hex");
if (!sigHeader || !timingSafeEqual(sigHeader, expected)) {
return res.status(400).send("Invalid webhook signature");
}
const event = JSON.parse(rawBody);
// TODO: handle event.type, event.data, etc.
res.status(200).send("ok");
}
4.2 Python example (FastAPI)
import hmac
import hashlib
import os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
SECRET = os.environ["MATCHAUDIT_WEBHOOK_SECRET"]
def verify_signature(raw_body: bytes, header: str | None) -> bool:
if not header:
return False
if not header.startswith("sha256="):
return False
received = header[len("sha256=") :]
digest = hmac.new(SECRET.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
# constant-time compare
return hmac.compare_digest(received, digest)
@app.post("/webhooks/matchaudit")
async def matchaudit_webhook(request: Request):
raw = await request.body()
sig = request.headers.get("x-matchaudit-signature")
if not verify_signature(raw, sig):
raise HTTPException(status_code=400, detail="Invalid signature")
event = await request.json()
# TODO: handle event["type"], event["data"], etc.
return {"ok": True}
5. Retries & idempotency
5.1 When MatchAudit retries
For each delivery attempt, we look at your HTTP response:
- 2xx – considered success. We do not retry this delivery.
- Non-2xx or network error/timeout – considered failure. We retry up to a maximum number of attempts with backoff.
Retries are scheduled in the background; you don't need to do anything special other than return a 2xx status when you've safely persisted the event.
5.2 Handling at-least-once delivery
Webhooks are delivered on an at-least-once basis. This means you may occasionally see the same event more than once (for example, if your server returned a 500 on the first attempt).
To make processing idempotent, we recommend:
- Use
event.id (also in x-matchaudit-event-id) as an idempotency key. - Before processing a webhook, check whether you have already handled that
event.id. - If you already processed it, skip side effects but still return
200 OK.
6. Testing webhooks
6.1 Using the Dashboard test button
In the Dashboard → Webhooks section you can:
- Create an endpoint and copy the signing secret into your server.
- Use “Send test” to trigger a
webhook.test event to your active endpoint. - Inspect recent deliveries and attempts in the dashboard and correlate with your server logs.
6.2 One-off tests to a specific URL
The test UI also lets you provide an explicit URL (for example, a webhook.site URL). In that case we send a single test event directly to that URL without requiring a configured endpoint. This is useful to quickly see the payload and headers your server will receive.
6.3 Local development tips
- Use a tunneling tool like ngrok or Cloudflare Tunnel to expose your local server, then set that public URL as your webhook endpoint.
- Log the raw body and headers on your endpoint until you're happy with signature verification, then reduce logging in production.