CentralCSP
FeaturesAlertingChannels

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

  1. In the dashboard, go to Alerts > Configuration > Add channel.
  2. Name the channel, choose the Webhook type, and enter your endpoint URL.
  3. Enter a signing secret of at least 8 characters. Generate a random one and store it where your endpoint can read it.
  4. 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/..."
}
FieldMeaning
idUnique to this request
eventIdThe alert. The same on every channel and every retry, so use it to deduplicate. null on a test
deliveryIdThis delivery to this channel. null on a test
testtrue for a test sent from the channel
titleThe headline every channel shows
website.tagsThe website's tags
findingsUp to 10 findings, each with a url to its evidence in the dashboard
findingsCountEvery finding of the alert, including any past the 10 listed
manageUrlThe 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:

verify.js
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

On this page