Webhooks
Get a signed HTTPS POST when Brix content is recalled or restored. Create endpoints, verify the brix-signature HMAC, handle retries, and replay deliveries.
A webhook endpoint receives a signed POST from Brix when an event happens in your workspace. Manage endpoints in the CMS at Settings > Integrations > Webhooks, or over the API. Managing webhooks needs integration.view and integration.edit at the workspace level.
Events
| Event | Sent when | data |
|---|---|---|
content.recalled | Someone pulls a file, design, playlist or schedule off every screen. | kind (media, creative, playlist, schedule), id, name (the kind’s label, such as Playlist), reason (or null), recalledBy |
content.restored | Recalled content is put back. | kind, id, name |
GET /v1/webhooks/events returns the full event catalog with a label and description for each. The catalog also names screen, approval and emergency events (screen.offline, screen.online, approval.requested, approval.decided, emergency.started, emergency.cleared). You can subscribe to them, but Brix does not send them. Only the two events above are delivered.
Create an endpoint
curl -X POST https://api.brixsignage.com/v1/webhooks \
-H "Authorization: Bearer $BRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/brix-hook", "events": ["content.recalled", "content.restored"], "description": "Ops channel"}'
The response is 201 with the endpoint (id starts with whe_) and its signing secret. The secret is shown once. Store it. An unknown event name returns 400.
| Call | What it does |
|---|---|
GET /v1/webhooks | List endpoints. The secret is never included. |
PATCH /v1/webhooks/{id} | Change url, events, description or enabled. Re-enabling clears an auto-disable. |
DELETE /v1/webhooks/{id} | Delete an endpoint. Its pending deliveries stop. |
POST /v1/webhooks/{id}/test | Send a real signed delivery now. |
GET /v1/webhooks/deliveries | The delivery log, with payload, response and attempts. Paginated. Kept 30 days. |
POST /v1/webhooks/deliveries/{id}/replay | Send a delivery again as a new delivery. |
The test delivery is a screen.offline event with data.test: true and a note that nothing is wrong. It returns { deliveryId, ok, status, error }. If it returns 409 not_subscribed, add screen.offline to the endpoint’s events.
The request Brix sends
POST /brix-hook HTTP/1.1
content-type: application/json
user-agent: Brix-Webhooks/1
x-brix-event: content.recalled
brix-signature: t=1790000000,v1=5f2b...c9
{
"deliveryId": "whd_...",
"event": "content.recalled",
"occurredAt": "2026-01-15T14:03:22.000Z",
"workspaceId": "...",
"attempt": 1,
"data": { "kind": "playlist", "id": "pl_...", "name": "Playlist", "reason": "Wrong prices", "recalledBy": "..." }
}
deliveryIdstays the same across retries. Use it to ignore duplicates.occurredAtis when the event happened, not when it was sent.attemptcounts delivery attempts, starting at 1.- A replay has a new
deliveryIdand areplayOffield with the original id.
Verify the signature
brix-signature is t=<unix seconds>,v1=<hex>. The hex is HMAC-SHA256 of <t>.<raw body> with your endpoint secret. Check it against the raw body bytes, before you parse the JSON, and reject old timestamps.
import crypto from 'node:crypto';
export function verifyBrix(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.trim().split('=', 2)));
const t = Number(parts.t);
if (!Number.isFinite(t) || !/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'));
}
import hmac, hashlib, time
def verify_brix(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Delivery and retries
- Delivery is at least once. Deduplicate on
deliveryId. - Any
2xxresponse is success. Redirects are not followed. Brix waits 10 seconds for a response. - A failed delivery is retried with backoff: 30 seconds, 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 2 hours, then 4 hours between attempts. After the ninth attempt the delivery is abandoned.
- After 9 failures in a row, Brix disables the endpoint and emails the workspace Owners. Fix the receiver, then set
enabled: trueagain. - Reply fast and do the work after. A slow receiver risks the 10-second timeout and a duplicate.