Skip to main content
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.

Overview and configuration

Webhooks are configured per organisation by the Adclear team during onboarding.
EventTriggerPayload
evaluation-completedAn evaluation finishes (success or failure)Issues, severity, cited rules, location, and your metadata
Delivery:
  • Sent as application/json via POST.
  • Make your endpoint idempotent, using evaluationId as the deduplication key, in case a result is delivered more than once.
  • Treat delivery as best-effort. If a webhook doesn’t arrive, fall back to polling the evaluation.
  • Every payload includes the metadata you provided at promotion creation, so you can correlate statelessly.

Signature verification

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));
});

Evaluation payload reference

Headers sent with every webhook:
HeaderDescription
Content-Typeapplication/json
X-Webhook-Signaturesha256=<hex> HMAC-SHA256 signature of the raw body
X-Adclear-EventEvent type, for example evaluation-completed
X-Correlation-IDRequest trace ID; include it in support requests
Example evaluation-completed payload:
{
  "organizationId": "org-uuid-...",
  "workspaceId": "ws-uuid-...",
  "evaluationId": "eval-uuid-...",
  "promotionId": "promo-uuid-...",
  "promotionVersionId": "version-uuid-...",
  "status": "succeeded",
  "reviewRequired": true,
  "overallAssessment": "This promotion requires review",
  "issues": [
    {
      "recommendation": "Add a risk warning stating that the value of investments can go down as well as up.",
      "rationale": "FCA COBS 4.6.2 requires a prominent risk warning for non-readily realisable securities.",
      "severity": "HIGH",
      "citedRules": [
        { "id": "rule-uuid-1", "name": "Risk Warning Required", "product": "ISA", "channel": "Email" }
      ],
      "location": { "type": "text", "content": "Invest today and earn guaranteed returns!", "pageNumber": 2 }
    }
  ],
  "metadata": { "workfrontProjectId": "PROJ-123", "workfrontTaskId": "TASK-456" }
}
organizationId
string
Your Adclear organisation ID.
workspaceId
string
The workspace the promotion belongs to.
evaluationId
string
Unique evaluation identifier. Use it as a deduplication key.
promotionId
string
The promotion that was evaluated.
promotionVersionId
string
The specific version that was evaluated.
status
string
succeeded or failed.
reviewRequired
boolean
true if the promotion has issues requiring human review.
overallAssessment
string
One-sentence summary suitable for a dashboard or notification.
issues
array
Ordered list of compliance findings. Empty when status is failed.
metadata
object
Your metadata, echoed back unchanged from promotion creation.
correlationId
string
Request trace ID. Include it in support requests.

Handling the result

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,
  });
}

Endpoint requirements

RequirementDetail
ProtocolHTTPS only. HTTP endpoints are rejected during configuration
Response timeRespond 2xx within 30 seconds. Queue longer processing
Response codeReturn 200 or 202 to acknowledge receipt
IdempotencyA result may be delivered more than once. Deduplicate on evaluationId
SignatureVerify X-Webhook-Signature before processing

Troubleshooting

  1. Confirm your endpoint is HTTPS. HTTP isn’t supported.
  2. Verify it responds with 2xx within 30 seconds.
  3. Check your signature verification. If it fails silently, you may be discarding valid payloads.
  4. 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.
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.
For cross-cutting errors (401, 403, 429, 502), see Errors & rate limits.