Webhooks let S4E push real-time event notifications to your application. When an event occurs --- a scan completes, a new finding is discovered, or an action finishes --- S4E sends an HTTP POST request to your configured endpoint.

Creating a Webhook

POST /api/webhooks

Request Body

Field Type Required Description
name string Yes Descriptive name for the webhook.
url string Yes HTTPS endpoint to receive events.
events array Yes List of event types to subscribe to.
secret string No HMAC-SHA256 secret for signature verification.
headers object No Custom headers to include in requests.
active boolean No Whether the webhook is active (default: true).

Example

curl -X POST "https://api.s4e.io/api/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Security Dashboard",
    "url": "https://dashboard.example.com/webhooks/s4e",
    "events": ["finding.new", "scan.completed"],
    "secret": "whsec_your_webhook_secret_here",
    "active": true
  }'

Response

{
  "data": {
    "id": "wh-001",
    "name": "Security Dashboard",
    "url": "https://dashboard.example.com/webhooks/s4e",
    "events": ["finding.new", "scan.completed"],
    "active": true,
    "created_at": "2026-04-28T12:00:00Z"
  }
}

Webhook Events

Event Description
scan.created A new scan has been created.
scan.completed A scan has finished.
scan.failed A scan has failed.
finding.new A new finding has been discovered.
finding.status_changed A finding's status has been updated.
action.triggered An action has been triggered.
action.completed An action has finished executing.
action.failed An action has failed.
playbook.started A playbook execution has started.
playbook.completed A playbook execution has finished.
playbook.failed A playbook execution has failed.
asset.created A new asset has been added.
asset.verified An asset has been verified.

Payload Format

Every webhook delivery follows this structure:

{
  "id": "evt-8a3b9c2d",
  "event": "finding.new",
  "timestamp": "2026-04-28T12:05:00Z",
  "data": {
    "finding_id": "f-91827",
    "title": "SQL Injection in /api/login",
    "severity": "critical",
    "cvss": 9.8,
    "asset": {
      "id": "a-1001",
      "name": "api.example.com"
    },
    "scan_id": "sc-44021"
  }
}

Webhook Security (HMAC-SHA256)

When you set a secret on a webhook, S4E signs every payload using HMAC-SHA256. The signature is sent in the X-S4E-Signature header.

Signature Format

X-S4E-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw request body>

Verifying Signatures --- Python

import hmac
import hashlib

def verify_signature(payload_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    received = signature_header.replace("sha256=", "")
    return hmac.compare_digest(expected, received)

Verifying Signatures --- Node.js

const crypto = require("crypto");

function verifySignature(payloadBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payloadBody)
    .digest("hex");
  const received = signatureHeader.replace("sha256=", "");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(received)
  );
}

Warning

Always verify webhook signatures before processing the payload. Without verification, an attacker could send forged events to your endpoint.

Retry Policy

If your endpoint returns a non-2xx status code or times out (30-second timeout), S4E retries delivery:

Attempt Delay
1 Immediate
2 1 minute
3 5 minutes
4 30 minutes

After 4 failed attempts, the delivery is marked as failed. You can view failed deliveries and manually retry them in the webhook management UI.

Managing Webhooks

List Webhooks

curl -X GET "https://api.s4e.io/api/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY"

Update Webhook

curl -X PUT "https://api.s4e.io/api/webhooks/wh-001" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["finding.new", "scan.completed", "action.completed"],
    "active": true
  }'

Delete Webhook

curl -X DELETE "https://api.s4e.io/api/webhooks/wh-001" \
  -H "Authorization: Bearer YOUR_API_KEY"

Test Webhook

Send a test event to verify your endpoint:

curl -X POST "https://api.s4e.io/api/webhooks/wh-001/test" \
  -H "Authorization: Bearer YOUR_API_KEY"

This sends a webhook.test event with sample data. Your endpoint should return 200 OK.

Delivery Logs

View recent webhook delivery attempts:

curl -X GET "https://api.s4e.io/api/webhooks/wh-001/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "data": [
    {
      "id": "del-001",
      "event": "finding.new",
      "status": "delivered",
      "response_code": 200,
      "duration_ms": 245,
      "timestamp": "2026-04-28T12:05:01Z"
    }
  ]
}

Best Practices

Practice Recommendation
Use HTTPS Always use HTTPS endpoints for webhook delivery.
Verify signatures Always validate the X-S4E-Signature header.
Respond quickly Return 200 immediately, process asynchronously.
Handle duplicates Use the id field to deduplicate events.
Monitor deliveries Check delivery logs for failures regularly.

Next Steps