Overview
Webhooks deliver events to your endpoint as they happen. Use them to trigger remediation workflows, update dashboards, or notify your team.
Events
| Event | Description |
|---|
scan.created | A new pentest has been started |
scan.completed | A pentest finished successfully |
scan.failed | A pentest failed during execution |
scan.cancelled | A pentest was cancelled |
vulnerability.created | A new vulnerability was found |
vulnerability.status_changed | A vulnerability status was updated |
vulnerability.severity_changed | A vulnerability severity was changed with an override reason |
* | Subscribe to all events |
Every delivery is a POST with a stable JSON envelope. The data object is event-specific and documented per event below.
| Field | Type | Description |
|---|
id | uuid | Unique delivery id. Also sent as X-Strix-Delivery. |
event | string | Webhook event type, such as scan.completed. |
created_at | ISO 8601 timestamp | Time the delivery was created. |
data | object | Event-specific payload. |
scan.created
A pentest was created and queued.
| Field | Type | Description |
|---|
scan_id | string | Scan id |
organization_id | string | Organization that owns the scan |
status | string | Current scan status |
title | string | null | Scan title |
created_at | ISO 8601 timestamp | Scan creation time |
{
"id": "delivery_123",
"event": "scan.created",
"created_at": "2026-05-19T04:30:00.000Z",
"data": {
"scan_id": "scan_123",
"organization_id": "org_123",
"status": "pending",
"title": "Production API pentest",
"created_at": "2026-05-19T04:30:00.000Z"
}
}
scan.completed, scan.failed, scan.cancelled
A pentest status changed. The three events share the same payload shape; status is completed, failed, or cancelled respectively.
| Field | Type | Description |
|---|
scan_id | string | Scan id |
organization_id | string | Organization that owns the scan |
status | string | New scan status |
previous_status | string | null | Previous scan status |
title | string | null | Scan title |
schedule_id | string | null | Schedule that launched the scan, when applicable |
updated_at | ISO 8601 timestamp | Time the status change was observed |
{
"id": "delivery_123",
"event": "scan.completed",
"created_at": "2026-05-19T05:12:00.000Z",
"data": {
"scan_id": "scan_123",
"organization_id": "org_123",
"status": "completed",
"previous_status": "running",
"title": "Production API pentest",
"schedule_id": "schedule_123",
"updated_at": "2026-05-19T05:12:00.000Z"
}
}
vulnerability.created
A vulnerability was created for a pentest.
| Field | Type | Description |
|---|
vulnerability_id | string | Vulnerability id |
organization_id | string | Organization that owns the vulnerability |
scan_id | string | Scan that found the vulnerability |
title | string | Vulnerability title |
severity | critical | high | medium | low | Current severity |
status | string | Current vulnerability status |
created_at | ISO 8601 timestamp | Vulnerability creation time |
{
"id": "delivery_123",
"event": "vulnerability.created",
"created_at": "2026-05-19T05:08:00.000Z",
"data": {
"vulnerability_id": "vuln_123",
"organization_id": "org_123",
"scan_id": "scan_123",
"title": "Unauthenticated account export",
"severity": "high",
"status": "open",
"created_at": "2026-05-19T05:08:00.000Z"
}
}
vulnerability.status_changed
A vulnerability status was changed.
| Field | Type | Description |
|---|
vulnerability_id | string | Vulnerability id |
organization_id | string | Organization that owns the vulnerability |
scan_id | string | null | Related scan, when present |
pr_review_id | string | null | Related PR review, when present |
status | string | New vulnerability status |
previous_status | string | null | Previous vulnerability status |
status_changed_at | ISO 8601 timestamp | null | Status change time |
status_changed_by | string | null | Actor that changed the status |
updated_at | ISO 8601 timestamp | Time the webhook event was created |
{
"id": "delivery_123",
"event": "vulnerability.status_changed",
"created_at": "2026-05-19T06:45:00.000Z",
"data": {
"vulnerability_id": "vuln_123",
"organization_id": "org_123",
"scan_id": "scan_123",
"pr_review_id": null,
"status": "fixed",
"previous_status": "open",
"status_changed_at": "2026-05-19T06:45:00.000Z",
"status_changed_by": "user@example.com",
"updated_at": "2026-05-19T06:45:00.000Z"
}
}
vulnerability.severity_changed
A vulnerability severity was changed with an override reason.
| Field | Type | Description |
|---|
vulnerability_id | string | Vulnerability id |
organization_id | string | Organization that owns the vulnerability |
scan_id | string | null | Related scan, when present |
pr_review_id | string | null | Related PR review, when present |
severity | critical | high | medium | low | New severity |
previous_severity | critical | high | medium | low | null | Previous severity |
original_severity | critical | high | medium | low | null | Original severity before overrides |
severity_changed_at | ISO 8601 timestamp | null | Severity change time |
severity_changed_by | string | null | Actor that changed the severity |
severity_override_reason | string | null | Reason supplied for the change |
updated_at | ISO 8601 timestamp | Time the webhook event was created |
{
"id": "delivery_123",
"event": "vulnerability.severity_changed",
"created_at": "2026-05-19T06:52:00.000Z",
"data": {
"vulnerability_id": "vuln_123",
"organization_id": "org_123",
"scan_id": "scan_123",
"pr_review_id": null,
"severity": "medium",
"previous_severity": "high",
"original_severity": "high",
"severity_changed_at": "2026-05-19T06:52:00.000Z",
"severity_changed_by": "user@example.com",
"severity_override_reason": "Compensating control is deployed.",
"updated_at": "2026-05-19T06:52:00.000Z"
}
}
Create a webhook
curl -X POST "https://app.strix.ai/api/v1/webhooks" \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/strix-webhook",
"events": ["scan.completed", "vulnerability.created"],
"is_active": true
}'
Required scope: webhooks:write
The response includes a secret field — store it securely. It will not be shown again.
Verify webhook signatures
Every webhook delivery includes signature headers for verification:
| Header | Description |
|---|
X-Strix-Event | The event type (e.g. scan.completed) |
X-Strix-Delivery | Unique delivery ID (use as idempotency key) |
X-Strix-Timestamp | ISO 8601 timestamp of when the event was sent |
X-Strix-Signature | HMAC-SHA256 signature for payload verification |
Signature verification (Node.js)
import crypto from "crypto";
export function verifyStrixWebhook(rawBody, headers, secret) {
const signature = headers["x-strix-signature"];
const timestamp = headers["x-strix-timestamp"];
if (!signature || !timestamp) return false;
const parsedTime = Date.parse(timestamp);
const ageMs = Math.abs(Date.now() - parsedTime);
if (!Number.isFinite(parsedTime) || ageMs > 5 * 60 * 1000) return false;
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
Always verify the signature before processing webhook payloads. Reject requests with expired timestamps (older than 5 minutes) to prevent replay attacks.
Delivery and retries
Events are delivered asynchronously with retries and exponential backoff. Use the X-Strix-Delivery header as an idempotency key when processing events to handle potential duplicate deliveries.
Manage webhooks
List webhooks
curl -X GET "https://app.strix.ai/api/v1/webhooks" \
-H "Authorization: Bearer <YOUR_API_TOKEN>"
Required scope: webhooks:read
Update a webhook
curl -X PATCH "https://app.strix.ai/api/v1/webhooks/<WEBHOOK_ID>" \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"events": ["*"], "is_active": true}'
Required scope: webhooks:write
Rotate webhook secret
curl -X PATCH "https://app.strix.ai/api/v1/webhooks/<WEBHOOK_ID>" \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"rotate_secret": true}'
The response includes the new secret. The old secret is invalidated immediately.
Required scope: webhooks:write
Delete a webhook
curl -X DELETE "https://app.strix.ai/api/v1/webhooks/<WEBHOOK_ID>" \
-H "Authorization: Bearer <YOUR_API_TOKEN>"
Required scope: webhooks:write
Inspect deliveries
curl -X GET "https://app.strix.ai/api/v1/webhooks/<WEBHOOK_ID>/deliveries?limit=25" \
-H "Authorization: Bearer <YOUR_API_TOKEN>"
Required scope: webhooks:read