Webhooks API
Webhooks endpoints in the Brix REST API: 8 operations (GET, POST, PATCH, DELETE), with auth, permissions and curl examples.
Base URL https://api.brixsignage.com. Send Authorization: Bearer $BRIX_API_KEY unless an operation says No auth. The permission chip names what the key must hold. See Authentication and scopes, Errors and rate limits and Pagination.
GET /v1/webhooksPOST /v1/webhooksPATCH /v1/webhooks/{id}DELETE /v1/webhooks/{id}POST /v1/webhooks/{id}/testGET /v1/webhooks/deliveriesPOST /v1/webhooks/deliveries/{id}/replayGET /v1/webhooks/events
GET/v1/webhooks
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.
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 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.
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. |
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. |
{
"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}
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.
| Parameter | 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. |
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). |
{
"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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Webhook endpoint id. |
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 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Webhook endpoint id. |
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 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.
| Parameter | 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. |
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
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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Id of the delivery to send again. |
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 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.
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. |