PXL Consent

Notifications and webhooks

PXL Consent can tell you when a scan finds something new or monitoring finds a problem. Add channels under Organisation → Notifications. Admins manage them, and each channel chooses its own events.

Events

EventSent when
scan.changedA scan found cookies, storage or third-party hosts that the previous scan did not, or no longer found some
incident.openedMonitoring found a problem on a site
incident.escalatedAn incident was not acknowledged within 72 hours
incident.resolvedA later check no longer found the problem
scan.no_consent_neededA scan found nothing on a site that needs the visitor's consent
scan.completedAny scan finished
scan.failedA scan could not finish

Slack

Create an incoming webhook for the channel in Slack (an app with Incoming Webhooks turned on) and paste its address, which starts with https://hooks.slack.com/. Messages are one line with a link to the dashboard.

Microsoft Teams

In the Teams channel, add the workflow Post to a channel when a webhook request is received and paste the address it gives you. Messages arrive as an adaptive card with a button to the dashboard.

Webhooks

A webhook receives a JSON POST for each event:

{
  "id": "evt_4f1c…",
  "event": "scan.changed",
  "created_at": "2026-09-16T12:00:00Z",
  "organization_id": 42,
  "site": { "id": 7, "public_id": "0f4c9e2a-…", "name": "Kunde AS" },
  "text": "Changes on Kunde AS. New since the last scan: _fbp (before consent).",
  "link": "https://consent.pxl.as/sites/7/cookies",
  "data": {
    "scan_id": 311, "previous_scan_id": 298,
    "added": [{ "kind": "cookie", "name": "_fbp", "domain": "kunde.no", "category": "marketing", "provider": "Meta", "before_consent": true }],
    "removed": []
  }
}

Incident events carry incident_id, kind, severity, summary and details in data.

Headers and signature

HeaderValue
X-PXL-EventThe event name
X-PXL-DeliveryA delivery number, the same on every retry
X-PXL-Signaturet=<unix time>,v1=<signature>

The signature is the hex HMAC-SHA256 of the time, a full stop and the raw request body, keyed with the channel's signing secret (shown in the channel's details). Check it before trusting the request, and reject requests whose time is more than five minutes old:

import crypto from 'node:crypto'

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

Delivery and retries

Any 2xx answer counts as delivered. Anything else is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours, then given up. A channel that fails 20 times in a row is turned off; turn it on again in its details once the receiver works. The details also list the latest deliveries and their errors. Use Test to send a message straight away.

Addresses must use https and point to the public internet.