Receive evaluation results automatically, verify their signatures, and handle the payload.
Webhooks deliver evaluation results to your endpoint as soon as they’re ready, so you don’t have to poll. You provide an endpoint URL during onboarding and receive a shared secret for verifying that requests genuinely came from Adclear.
Every request includes an X-Webhook-Signature header: an HMAC-SHA256 hex digest of the raw request body, prefixed with sha256=. Verify it before processing the payload. Use the raw bytes, not a re-serialised object, since whitespace differences change the digest.
import { createHmac, timingSafeEqual } from "node:crypto";export function verifyWebhookSignature(rawBody: string, signatureHeader: string, secret: string): boolean { const expected = createHmac("sha256", secret).update(rawBody).digest("hex"); const provided = signatureHeader?.startsWith("sha256=") ? signatureHeader.slice(7) : signatureHeader; // Reject anything that isn't a 64-char hex digest. Buffer.from(_, "hex") stops at // the first invalid character, so timingSafeEqual would otherwise throw on a // malformed or missing signature instead of returning false. if (!/^[0-9a-f]{64}$/i.test(provided ?? "")) return false; // Constant-time compare to avoid leaking the first differing byte via timing. return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"));}
Wire it into your endpoint using the raw body. Parsing to JSON first changes the bytes and invalidates the signature.
import express from "express";const app = express();app.post("/webhooks/adclear", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-webhook-signature"] as string; const rawBody = req.body.toString("utf-8"); if (!verifyWebhookSignature(rawBody, signature, process.env.ADCLEAR_WEBHOOK_SECRET!)) { res.status(401).json({ error: "Invalid signature" }); return; } // Acknowledge immediately, then process asynchronously to avoid timeouts. res.status(200).json({ received: true }); handleEvaluationCompleted(JSON.parse(rawBody));});
Acknowledge fast, then branch on status and reviewRequired. Generate the types from the OpenAPI spec (see Authentication).
function handleEvaluationCompleted(payload: EvaluationWebhookPayload): void { // Evaluation itself failed (for example, a corrupt file). No issues are returned. if (payload.status === "failed") { console.error(`Evaluation ${payload.evaluationId} failed. correlationId=${payload.correlationId}`); return; } // Passed compliance review — safe to auto-approve in your workflow. if (!payload.reviewRequired) { markApproved(payload.metadata?.workfrontTaskId); return; } // Issues found — prioritise by severity and link back via your metadata. const critical = payload.issues.filter((i) => i.severity === "HIGH"); createReviewTask({ taskId: payload.metadata?.workfrontTaskId, summary: payload.overallAssessment, criticalCount: critical.length, issues: payload.issues, });}
Confirm your endpoint is HTTPS. HTTP isn’t supported.
Verify it responds with 2xx within 30 seconds.
Check your signature verification. If it fails silently, you may be discarding valid payloads.
Confirm webhooks are configured for your organisation. Contact Adclear if unsure.
If a webhook still doesn’t arrive, poll the evaluation endpoint as a fallback.
Should I use webhooks or polling?
Either works. Webhooks avoid polling and timeout concerns. Polling (GET .../evaluation) is simpler to start with and useful as a fallback. Many integrations rely on webhooks and poll only if one is delayed. See Evaluation.