Claimscan
API OPERATIONALLog inTry it free
// WEBHOOKS

Webhook events

Subscribe to case lifecycle events with HMAC-SHA256 signed payloads. Configure endpoints from the Integrations page in the dashboard.

Event catalog

Three event types fire today. Adding a fourth is non-breaking — your endpoint receives only the events you subscribed to, so new types are opt-in.

EventWhen it fires
case.completedAnalysis pipeline finished for a case (any verdict).
case.failedPipeline crashed before producing a verdict — retry or reupload.
case.high_fraud_riskVerdict is LIKELY_MANIPULATED or LIKELY_AI_GENERATED with high confidence.

Payload envelope

Every event ships in a stable wrapper. The event-specific fields live under data; the wrapper itself never changes.

{
  "id": "evt_8a1c2d3e-4f5g-6789-abcd-ef0123456789",
  "type": "case.completed",
  "created": "2026-04-25T18:42:11.482Z",
  "data": {
    "caseId": "ce_01J9XJ3K4Q9R5S6T7U8V9W0X1Y",
    "verdict": "SUSPICIOUS",
    "confidence": 62,
    "criticalFindings": 0,
    "imageCount": 3,
    "completedAt": "2026-04-25T18:42:11.391Z"
  }
}

Signature verification

Every delivery carries an X-Claimscan-Signature header so you can prove the request really came from Claimscan and was not tampered with in transit. Reject any request that does not verify.

The header packs a unix timestamp and a hex-encoded HMAC digest, comma-separated:

X-Claimscan-Signature: t=1714069331,v1=8e2c9f4b1d7a6e3c0b8f5a2d9c1e6b4f3a7d8c2e5f9a1b4c7d0e3f6a9b2c5d8e

Verify each delivery in four steps:

  1. Parse the header into t (timestamp) and v1 (signature) parts.
  2. Reject if abs(now - t) > 300 seconds — protects against replay.
  3. Compute HMAC-SHA256 over `${t}.${rawBody}` using your endpoint secret. Use the raw request body, not a re-serialised JSON string.
  4. Compare to v1 with a constant-time comparison (timingSafeEqual in Node).

Reference implementation (Node)

import { createHmac, timingSafeEqual } from "node:crypto";

/**
 * Verify a Claimscan webhook signature. Reject if:
 *   - timestamp is older than 5 minutes (replay protection)
 *   - HMAC-SHA256 over `${timestamp}.${rawBody}` does not match v1
 */
export function verifyClaimscanSignature(opts: {
  header: string;        // X-Claimscan-Signature
  rawBody: string;       // request body BEFORE JSON.parse
  secret: string;        // whsec_<base64url>
}): boolean {
  const parts = Object.fromEntries(
    opts.header.split(",").map((p) => p.split("="))
  );
  const t = Number.parseInt(parts.t ?? "", 10);
  const v1 = parts.v1;
  if (!Number.isFinite(t) || !v1) return false;

  const skew = Math.abs(Math.floor(Date.now() / 1000) - t);
  if (skew > 5 * 60) return false;

  const expected = createHmac("sha256", opts.secret)
    .update(`${t}.${opts.rawBody}`)
    .digest();
  const actual = Buffer.from(v1, "hex");
  if (actual.length !== expected.length) return false;
  return timingSafeEqual(actual, expected);
}

Retry policy

Failed deliveries (network error or HTTP 4xx/5xx other than 410 Gone) retry up to four times with exponential backoff:

AttemptDelay after previous failure
1Immediate
2≈ 30 seconds
3≈ 5 minutes
4≈ 30 minutes

After the fourth failure the delivery is marked failed in your dashboard and we stop. Inspect the delivery log to retry manually after fixing your endpoint.

Security notes

  • Webhook URLs must be public HTTPS. We block private IP ranges, link-local and metadata addresses (SSRF guard) at registration time.
  • HTTP URLs are rejected. Self-signed certificates are not supported in production.
  • Treat the endpoint secret like an API key — store it server-side, never in client code, and rotate it from the dashboard if you suspect exposure.
  • Always treat deliveries as at-least-once. Use the envelope id as a deduplication key on your side.

Manage endpoints

Configure, test and rotate webhook endpoints under Integrations → Webhooks in the dashboard.