Webhook Events

POST /api/v1/webhooks

Webhook Events

Overview

The Webhook Events system enables real-time push notifications for platform events. Instead of polling API endpoints for changes, consumers register webhook URLs that receive HTTP POST callbacks when specific events occur. This is particularly valuable for security operations where timely notification of new findings, rating changes, or compliance status shifts can trigger automated response workflows.

Webhooks are organized into event categories that correspond to the platform's operational domains: perimeter events for EASM changes, agent events for fleet status changes, quality events for platform health shifts, and compliance events for regulatory assessment updates. Consumers subscribe to specific event types and receive only the notifications they care about.

Each webhook delivery includes a cryptographic signature that allows the receiver to verify the payload authenticity. Failed deliveries are retried with exponential backoff for up to 24 hours. The delivery status for each webhook is tracked and queryable through the management API.

Webhook Management Endpoints

Create a Webhook

POST /api/v1/webhooks

Register a new webhook endpoint to receive event notifications.

Request Body:

{
  "url": "https://ops.example.com/hooks/prismatic",
  "events": [
    "perimeter.rating_changed",
    "perimeter.critical_finding",
    "agents.health_changed",
    "compliance.status_changed"
  ],
  "secret": "whsec_your_signing_secret_here",
  "metadata": {
    "name": "Security Operations Webhook",
    "environment": "production"
  },
  "options": {
    "active": true,
    "retry_policy": "exponential",
    "max_retries": 10
  }
}

Response (201 Created):

{
  "ok": true,
  "data": {
    "webhook_id": "wh_abc123",
    "url": "https://ops.example.com/hooks/prismatic",
    "events": [
      "perimeter.rating_changed",
      "perimeter.critical_finding",
      "agents.health_changed",
      "compliance.status_changed"
    ],
    "active": true,
    "created_at": "2026-02-12T10:00:00.000Z"
  }
}

List Webhooks

GET /api/v1/webhooks

Returns all registered webhooks for the authenticated token.

Update a Webhook

PUT /api/v1/webhooks/:webhook_id

Modify webhook configuration (URL, events, active status).

Delete a Webhook

DELETE /api/v1/webhooks/:webhook_id

Remove a webhook and stop all deliveries.

Webhook Delivery History

GET /api/v1/webhooks/:webhook_id/deliveries

Returns the delivery history for a webhook, including status, response codes, and retry information.

Event Types

Perimeter Events

EventTriggerPayload Contains
perimeter.scan_completedDiscovery scan finishesScan summary, asset count, risk score
perimeter.rating_changedDomain security rating changesPrevious grade, new grade, delta, factors
perimeter.critical_findingCritical severity finding discoveredFinding details, affected asset, remediation
perimeter.asset_discoveredNew asset found on subsequent scanAsset type, value, discovery method
perimeter.asset_removedPreviously discovered asset no longer foundAsset details, last seen date
perimeter.certificate_expiringCertificate expires within 30 daysCertificate details, days until expiry

Agent Events

EventTriggerPayload Contains
agents.health_changedAgent health status changesAgent ID, previous status, new status, reason
agents.fleet_degradedMultiple agents degraded simultaneouslyAffected agents, degradation summary
agents.error_spikeAgent error rate exceeds thresholdAgent ID, error rate, threshold, sample errors
agents.process_crashAgent process crashes and restartsAgent ID, crash reason, restart count

Quality Events

EventTriggerPayload Contains
quality.score_changedPlatform quality score changesPrevious score, new score, affected domains
quality.gate_failedQuality gate check failsGate name, failure reason, affected files
quality.regression_detectedQuality regression detectedRegression type, affected metric, severity

Compliance Events

EventTriggerPayload Contains
compliance.status_changedCompliance status changes for a domainFramework, previous status, new status, requirements
compliance.new_gapNew compliance gap identifiedFramework, requirement ID, gap description
compliance.gap_resolvedPreviously identified gap is resolvedFramework, requirement ID, resolution evidence

Webhook Payload Format

Every webhook delivery follows this format:

{
  "webhook_id": "wh_abc123",
  "event_id": "evt_def456",
  "event_type": "perimeter.rating_changed",
  "timestamp": "2026-02-12T10:30:00.000Z",
  "data": {
    "domain": "example.com",
    "previous_rating": {
      "grade": "B",
      "score": 780
    },
    "current_rating": {
      "grade": "C",
      "score": 680
    },
    "delta": -100,
    "factors": [
      {
        "category": "certificate_management",
        "change": "Certificate expired on api.example.com",
        "impact": -80
      },
      {
        "category": "application_security",
        "change": "New staging environment exposed",
        "impact": -20
      }
    ]
  }
}

Payload Fields

FieldTypeDescription
webhook_idstringRegistered webhook identifier
event_idstringUnique event identifier (for deduplication)
event_typestringEvent type string
timestampstringISO 8601 timestamp when the event occurred
dataobjectEvent-specific payload (varies by event type)

Signature Verification

Every webhook delivery includes a signature header for payload authenticity verification:

X-Prismatic-Signature: sha256=abc123def456...
X-Prismatic-Timestamp: 1739347800

Verification Algorithm

  1. Concatenate the timestamp and raw request body: {timestamp}.{body}
  2. Compute HMAC-SHA256 using the webhook secret as the key
  3. Compare the result with the signature from the header

Verification Examples

Python:

import hmac
import hashlib

def verify_signature(payload_body, signature_header, timestamp_header, secret):
    expected = hmac.new(
        secret.encode("utf-8"),
        f"{timestamp_header}.{payload_body}".encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    provided = signature_header.replace("sha256=", "")
    return hmac.compare_digest(expected, provided)

Elixir:

def verify_signature(body, signature, timestamp, secret) do
  expected =
    :crypto.mac(:hmac, :sha256, secret, "#{timestamp}.#{body}")
    |> Base.encode16(case: :lower)

  provided = String.replace(signature, "sha256=", "")
  Plug.Crypto.secure_compare(expected, provided)
end

Authentication

Webhook management endpoints require a valid API token with webhooks:write scope for creation, modification, and deletion, and webhooks:read for listing and delivery history.

Authorization: Bearer <api_token>

Code Examples

curl -- Webhook Management

# Create a webhook
curl -s -X POST \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://ops.example.com/hooks/prismatic",
    "events": ["perimeter.rating_changed", "perimeter.critical_finding"],
    "secret": "whsec_my_secret"
  }' \
  http://localhost:4004/api/v1/webhooks | jq .

# List webhooks
curl -s -H "Authorization: Bearer $API_TOKEN" \
  http://localhost:4004/api/v1/webhooks | jq '.data.webhooks[]'

# Check delivery history
curl -s -H "Authorization: Bearer $API_TOKEN" \
  http://localhost:4004/api/v1/webhooks/wh_abc123/deliveries | jq '.data.deliveries[:5]'

# Delete a webhook
curl -s -X DELETE \
  -H "Authorization: Bearer $API_TOKEN" \
  http://localhost:4004/api/v1/webhooks/wh_abc123

Python -- Webhook Receiver

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_my_secret"

@app.route("/hooks/prismatic", methods=["POST"])
def handle_webhook():
    # Verify signature
    signature = request.headers.get("X-Prismatic-Signature", "")
    timestamp = request.headers.get("X-Prismatic-Timestamp", "")
    body = request.get_data(as_text=True)

    if not verify_signature(body, signature, timestamp, WEBHOOK_SECRET):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.json
    event_type = event["event_type"]

    if event_type == "perimeter.rating_changed":
        data = event["data"]
        print(f"Rating changed for {data['domain']}: "
              f"{data['previous_rating']['grade']} -> {data['current_rating']['grade']}")

    elif event_type == "perimeter.critical_finding":
        print(f"CRITICAL: {event['data']['description']}")

    return jsonify({"received": True}), 200

Retry Policy

Failed webhook deliveries are retried with exponential backoff:

AttemptDelayCumulative Time
1Immediate0
230 seconds30 seconds
32 minutes2.5 minutes
410 minutes12.5 minutes
530 minutes42.5 minutes
61 hour1h 42m
72 hours3h 42m
84 hours7h 42m
98 hours15h 42m
108 hours23h 42m

After 10 failed attempts (approximately 24 hours), the webhook is automatically deactivated. Reactivate it through the update endpoint after resolving the delivery issue.

A delivery is considered failed when the receiver returns a non-2xx status code or when the connection times out (10 second timeout per delivery attempt).

Error Responses

Status CodeError CodeDescription
400invalid_urlWebhook URL is not a valid HTTPS URL
400invalid_eventsOne or more event types are not recognized
401unauthorizedMissing or invalid authentication token
403insufficient_scopeToken lacks webhooks:write scope
404webhook_not_foundSpecified webhook_id does not exist
409duplicate_urlA webhook with this URL already exists
429rate_limitedWebhook management rate limit exceeded

Rate Limits

EndpointLimitWindow
POST /webhooks101 minute
GET /webhooks601 minute
PUT /webhooks/:id301 minute
DELETE /webhooks/:id301 minute
GET /webhooks/:id/deliveries601 minute

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL