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 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:

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, signed like any real delivery, so you can develop your verification against it.

Next steps

On this page