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 event, the rule, the website, and the findings:
{
"id": "0198f2c1-7d4e-7a21-b3aa-2f6f0c9d4e11",
"eventType": "csp-violation:new-type",
"rule": "New violation types",
"website": {
"id": "0198f2c1-1111-7a21-b3aa-2f6f0c9d4e22",
"name": "Shop",
"url": "https://shop.example.com"
},
"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
}
],
"findingsCount": 1,
"window": {
"start": "2026-08-27T09:00:00.000Z",
"end": "2026-08-27T09:15:00.000Z"
},
"dashboardUrl": "https://app.centralcsp.com/..."
}The findings array shape depends on eventType; each finding carries a kind matching the event. 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, 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.