# Webhooks

Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none.

## GET /v1/webhooks

List webhook endpoints

List the workspace's outbound webhook endpoints. The signing secret is never included in this response; it is shown only once, at creation.

**Notes.**
- Needs the permission at the workspace root: a location-scoped key is refused.

Auth: Bearer token. Permission: `integration.view`.

```bash
curl "https://api.brixsignage.com/v1/webhooks" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of WebhookEndpoint |  |
| `data[].id` | string | Webhook endpoint id. |
| `data[].url` | string | Where deliveries are POSTed. |
| `data[].description` | string \| null |  |
| `data[].secretPreview` | string | First 8 characters of the signing secret. |
| `data[].events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). |
| `data[].enabled` | boolean |  |
| `data[].autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. |
| `data[].autoDisabledReason` | string \| null |  |
| `data[].consecutiveFailures` | integer |  |
| `data[].lastDeliveryAt` | string \| null |  |
| `data[].lastStatus` | integer \| null | HTTP status of the last delivery attempt. |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data[].updatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/webhooks

Create a webhook endpoint

Create an endpoint. The response includes the signing secret once; after that, only a preview of it is available, since it is stored encrypted and cannot be retrieved in full. The endpoint URL is checked at creation and again before every delivery, since a hostname that resolved to a public address at save time could later be re-pointed at an internal address. Only `content.recalled` and `content.restored` events are actually sent today, even though the event catalog lists more.

The response carries the signing `secret` ONCE. Store it: later reads return only `secretPreview`.

**Notes.**
- Validation failures are 400 `bad_request`, where most other routes answer 422 `validation_error`.

Auth: Bearer token. Permission: `integration.edit`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | An https URL on the public internet (private and loopback hosts are refused). |
| `events` | array of "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" | no | Events to deliver. Default: none. An unknown name is refused. |
| `description` | string | no | Label; cut to 200 characters. |

```bash
curl -X POST "https://api.brixsignage.com/v1/webhooks" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/brix","events":["screen.offline","screen.online"],"description":"Ops alerts"}'
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string | Webhook endpoint id. |
| `data.url` | string | Where deliveries are POSTed. |
| `data.description` | string \| null |  |
| `data.secretPreview` | string | First 8 characters of the signing secret. |
| `data.events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). |
| `data.enabled` | boolean |  |
| `data.autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. |
| `data.autoDisabledReason` | string \| null |  |
| `data.consecutiveFailures` | integer |  |
| `data.lastDeliveryAt` | string \| null |  |
| `data.lastStatus` | integer \| null | HTTP status of the last delivery attempt. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.secret` | string | The HMAC signing secret (64 hex characters). Shown only in this response. |

```json
{
  "data": {
    "id": "whe_7a8b9c0d1e2f3a4b",
    "url": "https://hooks.example.com/brix",
    "description": "Ops alerts",
    "secretPreview": "3f9a1c2e",
    "events": [
      "screen.offline",
      "screen.online"
    ],
    "enabled": true,
    "autoDisabledAt": null,
    "autoDisabledReason": null,
    "consecutiveFailures": 0,
    "lastDeliveryAt": null,
    "lastStatus": null,
    "createdAt": "2026-09-28T09:00:00.000Z",
    "updatedAt": "2026-09-28T09:00:00.000Z",
    "secret": "3f9a1c2e4b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f"
  }
}
```

Response 400: `bad_request` (missing url, unknown event) or `unsafe_url`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## PATCH /v1/webhooks/{id}

Update a webhook endpoint

Change a webhook endpoint's URL, event subscriptions, description, or enabled state. Re-enabling an endpoint clears any automatic disable and resets its failure count, since re-enabling means the receiver has been confirmed fixed.

**Notes.**
- Needs the permission at the workspace root: a location-scoped key is refused.
- Validation failures are 400 `bad_request`, where most other routes answer 422 `validation_error`.

Auth: Bearer token. Permission: `integration.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Webhook endpoint id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | no | An https URL on the public internet. |
| `events` | array of "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" | no | Replaces the subscription. An unknown name is refused. |
| `description` | string | no | Cut to 200 characters. |
| `enabled` | boolean | no | `true` also clears an automatic switch-off and the failure count. |

```bash
curl -X PATCH "https://api.brixsignage.com/v1/webhooks/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}'
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | WebhookEndpoint | A customer webhook endpoint. The signing secret is never returned after creation. |
| `data.id` | string | Webhook endpoint id. |
| `data.url` | string | Where deliveries are POSTed. |
| `data.description` | string \| null |  |
| `data.secretPreview` | string | First 8 characters of the signing secret. |
| `data.events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). |
| `data.enabled` | boolean |  |
| `data.autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. |
| `data.autoDisabledReason` | string \| null |  |
| `data.consecutiveFailures` | integer |  |
| `data.lastDeliveryAt` | string \| null |  |
| `data.lastStatus` | integer \| null | HTTP status of the last delivery attempt. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |

```json
{
  "data": {
    "id": "whe_7a8b9c0d1e2f3a4b",
    "url": "https://hooks.example.com/brix",
    "description": "Ops alerts",
    "secretPreview": "3f9a1c2e",
    "events": [
      "screen.offline",
      "screen.online"
    ],
    "enabled": true,
    "autoDisabledAt": null,
    "autoDisabledReason": null,
    "consecutiveFailures": 0,
    "lastDeliveryAt": null,
    "lastStatus": null,
    "createdAt": "2026-09-28T09:00:00.000Z",
    "updatedAt": "2026-09-28T09:00:00.000Z"
  }
}
```

Response 400: `bad_request` (unknown event) or `unsafe_url`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such endpoint in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## DELETE /v1/webhooks/{id}

Delete a webhook endpoint. Any deliveries still pending for it are abandoned rather than retried.

**Notes.**
- Answers `{ data: { id } }` — without the `deleted: true` most other deletes return.

Auth: Bearer token. Permission: `integration.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Webhook endpoint id. |

```bash
curl -X DELETE "https://api.brixsignage.com/v1/webhooks/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such endpoint in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/webhooks/{id}/test

Send a test webhook delivery

Send a real, signed sample delivery to this endpoint now. It is a genuine delivery rather than a simulation, so it exercises the same signature verification your receiver uses in production. The sample event is `screen.offline`; this call returns 409 if the endpoint is not subscribed to that event.

Sends a real, signed `screen.offline` delivery with `data: { test: true, note }` now and records it in the delivery log. A failed send is NOT an HTTP error: the answer is 200 with `ok: false`.

**Notes.**
- The test event is always `screen.offline`: an endpoint subscribed only to other events gets 409 `not_subscribed`, with a message that says it is subscribed to no event.
- Needs the permission at the workspace root: a location-scoped key is refused.

Auth: Bearer token. Permission: `integration.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Webhook endpoint id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/webhooks/{id}/test" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.deliveryId` | string | Id of the delivery this call made (a new `whd_…` row in the delivery log). |
| `data.ok` | boolean | The receiver answered 2xx. |
| `data.status` | integer \| null | HTTP status the receiver answered with; null when no answer came. |
| `data.error` | string \| null | Why the delivery failed (`http 500`, a timeout, an unsafe address); null on success. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such endpoint in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `not_subscribed`: the endpoint is not subscribed to `screen.offline`, or it is switched off.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/webhooks/deliveries

List webhook deliveries

List the webhook delivery log: what was sent, the response received, the number of attempts, and what is still pending. Each entry includes the exact payload that was sent. Results are paginated and can be filtered by endpoint and status. Failed deliveries are retried with increasing delay over roughly 8 to 12 hours before they are given up on.

**Notes.**
- Always paged: without `?limit` a page has 50 deliveries (not every row, unlike the lists that page only on request), and `nextCursor` is always present.
- Needs the permission at the workspace root: a location-scoped key is refused.

Auth: Bearer token. Permission: `integration.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | string | no | Page size, 1–200. Default 50. |
| `cursor` | query | string | no | The `nextCursor` of the previous page. |
| `endpointId` | query | string | no | Only this endpoint's deliveries. |
| `status` | query | "pending" \| "delivered" \| "failed" \| "abandoned" | no | Only deliveries in this state. Another value is ignored. |

```bash
curl "https://api.brixsignage.com/v1/webhooks/deliveries" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of WebhookDelivery | Newest first. |
| `data[].id` | string | Webhook delivery id. |
| `data[].endpointId` | string |  |
| `data[].event` | string |  |
| `data[].status` | "pending" \| "delivered" \| "failed" \| "abandoned" | `failed` is retried later; `abandoned` is not retried again. |
| `data[].attempts` | integer |  |
| `data[].occurredAt` | string | ISO-8601 timestamp (UTC). |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data[].nextAttemptAt` | string \| null | When the next retry is due; null when none is. |
| `data[].deliveredAt` | string \| null |  |
| `data[].responseStatus` | integer \| null |  |
| `data[].error` | string \| null |  |
| `data[].replayOf` | string \| null | Set on a delivery made by a replay: the delivery it resends. |
| `data[].payload` | object \| null | The JSON body as it was sent; null if the stored copy cannot be read. |
| `nextCursor` | string \| null | Send it back as `?cursor=` for the next page; null on the last page. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/webhooks/deliveries/{id}/replay

Replay a webhook delivery

Resend a delivery as a new delivery with its own id and a `replayOf` field pointing at the original. The original delivery record is left unchanged, so a receiver that deduplicates on delivery id will not silently ignore the resend.

Sends the payload again now, as a new delivery. A failed send is NOT an HTTP error: the answer is 200 with `ok: false`.

**Notes.**
- Needs the permission at the workspace root: a location-scoped key is refused.

Auth: Bearer token. Permission: `integration.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Id of the delivery to send again. |

```bash
curl -X POST "https://api.brixsignage.com/v1/webhooks/deliveries/{id}/replay" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.deliveryId` | string | Id of the delivery this call made (a new `whd_…` row in the delivery log). |
| `data.ok` | boolean | The receiver answered 2xx. |
| `data.status` | integer \| null | HTTP status the receiver answered with; null when no answer came. |
| `data.error` | string \| null | Why the delivery failed (`http 500`, a timeout, an unsafe address); null on success. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such delivery in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/webhooks/events

List subscribable webhook events

List the catalog of event types that can be subscribed to, each with a label and a description of what it carries. Only `content.recalled` and `content.restored` events are actually sent today; other listed events are not yet emitted.

Auth: Bearer token. Permission: `integration.view`.

```bash
curl "https://api.brixsignage.com/v1/webhooks/events" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].event` | "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" |  |
| `data[].label` | string |  |
| `data[].what` | string | What the event means and carries. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |
