> ## Documentation Index
> Fetch the complete documentation index at: https://docs.app.strix.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Subscribe to pentest and vulnerability lifecycle events

export const ScarfPixel = () => <img referrerPolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=831669b6-b4bd-42cb-8832-2122c03e1d5f" alt="" width="1" height="1" style={{
  position: "absolute",
  width: 0,
  height: 0,
  opacity: 0,
  pointerEvents: "none"
}} />;

<ScarfPixel />

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

## Payload format

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              |

```json theme={null}
{
  "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              |

```json theme={null}
{
  "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              |

```json theme={null}
{
  "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       |

```json theme={null}
{
  "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       |

```json theme={null}
{
  "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

```bash theme={null}
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.

## Per-business\_unit routing

By default a subscription is **org-wide**: it fires for every matching event in your organization. On Enterprise plans you can scope a subscription to a single `business_unit` so it only receives events whose assets belong to that business unit — useful when you run multiple end-customers in one organization and want each to receive only their own events.

```bash theme={null}
curl -X POST "https://app.strix.ai/api/v1/webhooks" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://acme.example.com/strix-webhook",
    "events": ["*"],
    "business_unit": "acme"
  }'
```

* Omit `business_unit` (or set it to `null`) for an org-wide subscription — this is the default and matches existing behavior.
* Set `business_unit` to a string to receive only events whose resolved business units include that value. An event's business units are derived from the assets (domains/repositories) involved.
* Setting `business_unit` requires the Enterprise plan (the `rbac` feature). Requests on other plans return `403`.
* To convert a scoped subscription back to org-wide, `PATCH` it with `"business_unit": null`.

<Note>
  Set an asset's `business_unit` via `POST`/`PATCH` on `/v1/domains` and `/v1/repositories`. Tokens scoped to a `business_unit` only see and manage that unit's assets, scans, and vulnerabilities.
</Note>

## 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)

```javascript theme={null}
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)
  );
}
```

<Warning>
  Always verify the signature before processing webhook payloads. Reject requests with expired timestamps (older than 5 minutes) to prevent replay attacks.
</Warning>

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

```bash theme={null}
curl -X GET "https://app.strix.ai/api/v1/webhooks" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"
```

**Required scope:** `webhooks:read`

### Update a webhook

```bash theme={null}
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

```bash theme={null}
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

```bash theme={null}
curl -X DELETE "https://app.strix.ai/api/v1/webhooks/<WEBHOOK_ID>" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"
```

**Required scope:** `webhooks:write`

### Inspect deliveries

```bash theme={null}
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`
