CasePrimus.aiby Attestr
Documentation menu
API guide

Webhooks: real-time court case alerts in your own system

A webhook is a message CasePrimus.ai sends to a web address you control the moment something changes: a new case is found, a hearing date moves, a risk score shifts. Instead of checking a dashboard or polling the API, your systems hear about it straight away.

Overview

Webhooks suit teams that want court-case data to flow into a system they already run, such as a CRM, a vendor-risk tool or an internal alerting pipeline. You choose which of the 13 event types a webhook receives. Each event arrives as a JSON POST request, signed so you can verify it.

Set up a webhook

  1. Open the Developer console in your CasePrimus.ai dashboard and go to Webhooks.
  2. Create a webhook with the HTTPS address of your endpoint. Plain HTTP is not accepted, because the signature only protects data that already travels over an encrypted connection.
  3. Choose the events to receive. You can change this later.
  4. Copy the signing secret when it is shown. It is shown once, straight after you create the webhook or regenerate its secret. If you lose it, regenerate it; the old one stops working immediately.
  5. Optionally add your public key to have every delivery encrypted.

What a delivery looks like

Every webhook delivery is a JSON object with the same envelope. The event-specific details are in `data`.

FieldDescription
id
string
Unique id of the event. It stays the same when a delivery is retried, so you can use it to recognise a repeat.
type
string
The event type, for example case.added.
createdAt
string (ISO 8601)
When the event happened.
accountId
string
Your CasePrimus.ai account.
data
object
Details of the event: the resource it relates to, and a link back to it in the dashboard.

Every request also carries these headers:

Content-Typeapplication/json
User-AgentCasePrimus-Webhooks/1.0
X-CasePrimus-EventThe event type, for example case.added.
X-CasePrimus-DeliveryThe id of this delivery.
X-CasePrimus-Signaturet=<timestamp>,v1=<signature>. See below.
Example: case.added
{
  "id": "evt_01HZY3K9G8X0000000000002",
  "type": "case.added",
  "createdAt": "2026-09-13T09:15:00.000Z",
  "accountId": "CPA0-Bq993yz3k4IVeK",
  "data": {
    "resourceType": "CASE",
    "resourceId": "CPWLC0-4d71b2",
    "deepLink": "/watchlist/case/CPWLC0-4d71b2",
    "caseReference": "DLHC01-004821-2026"
  }
}

Verify the signature

Every delivery is signed. The signature is an HMAC-SHA256, in hexadecimal, of the string {timestamp}.{raw body}, made with your webhook's signing secret. Always verify it against the raw request body, before parsing the JSON, and reject deliveries whose timestamp is more than a few minutes old.

Verify a delivery
const crypto = require("crypto");

function verifyCasePrimusSignature(rawBody, signatureHeader, signingSecret) {
  const match = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signatureHeader ?? "");
  if (!match) throw new Error("Malformed X-CasePrimus-Signature header");
  const [, timestamp, signature] = match;

  const ageMs = Date.now() - Number(timestamp) * 1000;
  if (ageMs > 5 * 60 * 1000) throw new Error("Signature too old - possible replay");

  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const expectedBuf = Buffer.from(expected);
  const signatureBuf = Buffer.from(signature);
  const valid = expectedBuf.length === signatureBuf.length
    && crypto.timingSafeEqual(expectedBuf, signatureBuf);
  if (!valid) throw new Error("Invalid signature");

  return JSON.parse(rawBody);
}

// Express example. It needs the RAW body, so this route must NOT use
// express.json() ahead of it.
app.post("/webhooks/caseprimus", express.raw({ type: "application/json" }), (req, res) => {
  const event = verifyCasePrimusSignature(
    req.body.toString("utf8"),
    req.header("X-CasePrimus-Signature"),
    process.env.CASEPRIMUS_WEBHOOK_SECRET
  );
  // handle event.type and event.data
  res.sendStatus(200);
});

If encryption is on

The body is an encrypted envelope rather than the plain event. The signature still covers the body exactly as it was sent. Decrypt it with your private key after verifying.

Responses and retries

Return any 2xx status within 10 seconds to acknowledge a delivery. Do slow work after you respond. Anything else, including a timeout, counts as a failure and the delivery is retried, up to 6 attempts in total, waiting 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours between attempts.

Endpoints that keep failing are paused

After 5 deliveries in a row have used up all their attempts, the webhook is switched off automatically and your account is notified, so a dead endpoint is not called forever. Fix the endpoint and switch the webhook back on from the Developer console.

Because a delivery can be retried, the same event may reach you more than once. The id in the envelope stays the same, so use it to ignore repeats.

Events you can subscribe to

Frequently asked questions

How do I receive CasePrimus.ai alerts in my own system?

Create a webhook in the Developer console of your CasePrimus.ai dashboard, give it an HTTPS endpoint, and choose the events you want. CasePrimus.ai then sends each event to that endpoint as a signed JSON POST request, so you don't need to poll the API.

How can I be sure a webhook really came from CasePrimus.ai?

Every delivery carries an X-CasePrimus-Signature header: an HMAC-SHA256 of the timestamp and the raw request body, made with your webhook's signing secret. Recompute it on your side and compare. Code for Node.js and Python is on this page.

What happens if my endpoint is down?

A delivery counts as successful only when your endpoint returns a 2xx status within 10 seconds. Otherwise it is retried, up to 6 attempts in total, with waits of 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours between attempts.

Can webhook payloads be encrypted?

Yes, optionally. If you add your public key to a webhook, each delivery body becomes an encrypted envelope that only your matching private key can open, on top of HTTPS and the signature.

Last updated 14 October 2026