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
- Open the Developer console in your CasePrimus.ai dashboard and go to Webhooks.
- 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.
- Choose the events to receive. You can change this later.
- 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.
- 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`.
| Field | Description |
|---|---|
idstring | Unique id of the event. It stays the same when a delivery is retried, so you can use it to recognise a repeat. |
typestring | The event type, for example case.added. |
createdAtstring (ISO 8601) | When the event happened. |
accountIdstring | Your CasePrimus.ai account. |
dataobject | 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-Type | application/json |
User-Agent | CasePrimus-Webhooks/1.0 |
X-CasePrimus-Event | The event type, for example case.added. |
X-CasePrimus-Delivery | The id of this delivery. |
X-CasePrimus-Signature | t=<timestamp>,v1=<signature>. See below. |
{
"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.
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); });
import hmac import hashlib import json import re import time def verify_caseprimus_signature(raw_body: bytes, signature_header: str, signing_secret: str) -> dict: match = re.match(r"^t=(\d+),v1=([0-9a-f]+)$", signature_header or "") if not match: raise ValueError("Malformed X-CasePrimus-Signature header") timestamp, signature = match.groups() age_seconds = time.time() - int(timestamp) if age_seconds > 5 * 60: raise ValueError("Signature too old - possible replay") message = f"{timestamp}.{raw_body.decode()}".encode() expected = hmac.new(signing_secret.encode(), message, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, signature): raise ValueError("Invalid signature") return json.loads(raw_body)
If encryption is on
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
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
Case events
A case was found, changed, summarized or re-scored.
6 events
Entity events
A company or person on your watchlist changed, finished a historical scan, or was archived.
3 events
Task events
A task in your workspace was assigned, completed or became overdue.
3 events
Historical search events
An asynchronous historical search finished.
1 event
See every event with its payload on the events reference.
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