Skip to content

Receive webhooks

Webhooks are NovaVMS’s push channel. Instead of polling GET /api/v1/events, you register an HTTPS URL, pick the event types you care about, and NovaVMS POSTs a signed JSON body every time one fires. Webhook definitions are created by operators, but the signing secret is only visible to admins and owners (D81). This page is about the receiver side; for the admin UI flow see Create and rotate webhooks.

Register a URL

An admin or operator creates the webhook at /admin/webhooks — full UI flow in Create and rotate webhooks. You can also do it over the API (since v1.0):

Terminal window
curl -X POST -H "Authorization: Bearer sk_live_abc123" \
-H "Content-Type: application/json" \
https://novavms.novalien.com/api/v1/webhooks \
-d '{
"name": "SOC alerts",
"url": "https://hooks.example.com/novavms",
"event_types": ["alert", "event"],
"enabled": true
}'
// @novavms/sdk >= 1.0.0
const webhook = await novavms.webhooks.create({
name: 'SOC alerts',
url: 'https://hooks.example.com/novavms',
event_types: ['alert', 'event'],
enabled: true,
});
console.log(webhook.secret); // shown once
# novavms >= 1.0.0
wh = novavms.webhooks.create(
name="SOC alerts",
url="https://hooks.example.com/novavms",
event_types=["alert", "event"],
enabled=True,
)
print(wh.secret) # shown once

Expected response:

{
"id": "e2d5b3a1-9876-4321-abcd-0123456789ab",
"name": "SOC alerts",
"url": "https://hooks.example.com/novavms",
"event_types": ["alert", "event"],
"enabled": true,
"secret": "whsec_live_7c4a1d9e8b2f3a5c6d9e0f1a2b3c4d5e",
"created_at": "2026-04-21T10:00:00Z"
}

The secret is returned exactly once. Store it in your receiver’s secret manager — you use it to verify every delivery (see below).

Sample delivery

Every delivery is a POST with Content-Type: application/json and the following headers (since v1.0):

HeaderExamplePurpose
X-Webhook-IDa9f3c1e2-0000-4000-8000-000000000001Unique per delivery; use for idempotency
X-Webhook-Timestamp2026-04-21T14:05:00ZISO 8601; reject deliveries > 5 min old to prevent replay
X-Webhook-EventalertOne of alert, event, camera_status, gateway_status
X-Webhook-Signature3a8f... (64 hex chars)HMAC-SHA256 of the raw body with the secret

Sample body for event_type=alert:

{
"webhook_id": "a9f3c1e2-0000-4000-8000-000000000001",
"event_type": "alert",
"delivered_at": "2026-04-21T14:05:00Z",
"org_id": "c2d3e4f5-a6b7-4c8d-9e0f-112233445566",
"data": {
"alert_id": "550e8400-e29b-41d4-a716-446655440000",
"rule_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"rule_name": "Loading dock motion after hours",
"event_id": "9a7e8b14-3c2d-4e5f-8a9b-1c2d3e4f5a6b",
"camera_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"camera_name": "Loading Dock Camera",
"confidence": 0.92,
"triggered_at": "2026-04-21T14:04:58Z",
"event_url": "https://novavms.novalien.com/events/9a7e8b14-3c2d-4e5f-8a9b-1c2d3e4f5a6b"
}
}

Respond fast

Return 2xx within 10 seconds. Anything slower counts as a timeout and triggers a retry. Do the minimum inline (signature check, enqueue) and process asynchronously.

Retries

On 5xx or timeout, NovaVMS retries with exponential backoff at 1s, 10s, 1m, 10m, 1h (5 attempts total). Each retry carries the same X-Webhook-ID so your handler can deduplicate. On 4xx (except 408, 429), NovaVMS does not retry — a client-error response means “don’t deliver this to me, ever.”

After 5 consecutive deliveries fail, NovaVMS auto-disables the webhook (enabled=false) and surfaces [ERR] in the admin UI. You must investigate, then flip the toggle back on.

Verify the signature

Never trust the body until you verify X-Webhook-Signature. Full walkthrough in Verify webhook signatures — Node.js and Python implementations with timing-safe compare.