Webhook
Deliver CentralCSP alerts to your own endpoint as signed JSON. Payload shape, HMAC-SHA256 signature headers, and verification code.
Last update:
A webhook channel POSTs each alert as JSON to an endpoint you run, signed with a shared secret so you can verify it came from us. Use it for anything the built-in chat types do not cover, such as PagerDuty or Opsgenie ingestion, a ticketing system, or your own automation.
Prerequisites
- The website Manager role in CentralCSP.
- An https endpoint on a public host that accepts POST requests. Internal and private addresses are rejected, including
localhost,.local,.internal, hosts with no dot, and anything resolving to a private IP range.
1. Add the channel
- In the dashboard, go to Alerts > Configuration > Add channel.
- Name the channel, choose the Webhook type, and enter your endpoint URL.
- Enter a signing secret of at least 8 characters. Generate a random one and store it where your endpoint can read it.
- Save.
The secret is stored and never returned by the API. Editing the channel later shows a masked preview; leave the field empty to keep the current value.
2. Handle the request
Each delivery is a POST with a JSON body and two headers:
POST /your-endpoint HTTP/1.1
content-type: application/json
x-centralcsp-timestamp: 1754640000000
x-centralcsp-signature: sha256=8b1a9953c4611296a827abf8c47804d7...The body carries the alert, the rule, the website, and the findings:
{
"id": "0198f2c1-9a10-7c3e-8d21-5b7e0f4a6c90",
"eventId": "0198f2c1-7d4e-7a21-b3aa-2f6f0c9d4e11",
"deliveryId": "0198f2c1-8b2f-7e45-a1c3-9d0e4f5a6b72",
"test": false,
"eventType": "csp-violation:new-type",
"rule": "New violation types",
"title": "New CSP violation: cdn.evil.example (script-src) · blocked",
"website": {
"id": "0198f2c1-1111-7a21-b3aa-2f6f0c9d4e22",
"name": "Shop",
"url": "https://shop.example.com",
"tags": ["Production"]
},
"findings": [
{
"kind": "csp-violation:new-type",
"key": "script-src\nhttps://cdn.evil.example",
"effectiveDirective": "script-src",
"blockedUrlOrigin": "https://cdn.evil.example",
"firstSeen": "2026-08-27T09:12:00.000Z",
"count": 41,
"firstPage": "https://shop.example.com/checkout",
"exampleBlockedUrl": "https://cdn.evil.example/loader.js",
"disposition": "enforce",
"blockedCount": 41,
"reportedCount": 0,
"url": "https://app.centralcsp.com/..."
}
],
"findingsCount": 1,
"window": {
"start": "2026-08-27T09:00:00.000Z",
"end": "2026-08-27T09:15:00.000Z"
},
"dashboardUrl": "https://app.centralcsp.com/...",
"manageUrl": "https://app.centralcsp.com/..."
}| Field | Meaning |
|---|---|
id | Unique to this request |
eventId | The alert. The same on every channel and every retry, so use it to deduplicate. null on a test |
deliveryId | This delivery to this channel. null on a test |
test | true for a test sent from the channel |
title | The headline every channel shows |
website.tags | The website's tags |
findings | Up to 10 findings, each with a url to its evidence in the dashboard |
findingsCount | Every finding of the alert, including any past the 10 listed |
manageUrl | The rule's delivery history in the dashboard |
The findings array shape depends on eventType; each finding carries a kind matching the event. Policy findings add disposition (enforce when the browser blocked, report when it only reported, both) with blockedCount and reportedCount. Technology findings add their advisories (cves, each with id, severity, summary, fixedIn, and url), upgradeTo and upgradeReason, which is replace with a null upgradeTo when no maintained release exists. The API reference documents every finding type. Respond with any 2xx status. Anything else counts as a failure: redirects are not followed, and after five consecutive failures the channel disables itself.
3. Verify the signature
The signature is an HMAC-SHA256 of `${timestamp}.${body}` using your secret, hex encoded, prefixed with sha256=. Verify it, and reject stale timestamps to block replays:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, headers, rawBody) {
const timestamp = headers["x-centralcsp-timestamp"];
const received = headers["x-centralcsp-signature"];
if (!timestamp || !received) return false;
// Reject anything older than 5 minutes.
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) return false;
const expected =
"sha256=" +
createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return (
expected.length === received.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(received))
);
}Compute the HMAC over the raw request body, before any JSON parsing; a re-serialized body will not match.
4. Test the channel
Select Test on the new channel. A sample alert built from a fake CSP violation is sent immediately, with test set to true and signed like any real delivery, so you can develop your verification against it.
Next steps
- Create a rule that delivers to this channel.
- Channels overview for auto-disable behaviour and limits.
- Delivery history to audit what was sent.