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-IDe2d5b3a1-9876-4321-abcd-0123456789abWebhook UUID (constant across retries AND across distinct deliveries for the same webhook — NOT a per-delivery identifier). For idempotency, use the per-event UUID inside the body (alert_id, event_id, camera_id, or gateway_id).
X-Webhook-Timestamp2026-04-21T14:05:00ZRFC 3339 UTC. 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, hex-encoded (not base64url).

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 any non-2xx (or timeout), NovaVMS retries up to 5 times with exponential backoff capped at one hour. Each retry carries the same body and the same X-Webhook-ID header (which is the webhook UUID, not a per-delivery identifier — use the body’s per-event identifier for idempotency). On 401 / 403, NovaVMS short-circuits and does not retry.

Note (2026-05-22): exact backoff constants vary across the delivery code path (1s/10s/1m/10m/1h vs 1s/5s/30s family) — the Phase 7 audit logged the discrepancy as GAP-8-13. Treat as “5 retries with exponential backoff capped at one hour” rather than relying on specific intervals.

After repeated consecutive delivery failures, NovaVMS auto-disables the webhook (enabled=false) and surfaces a degraded state in the admin UI. You must investigate, then re-enable from /admin/webhooks.

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.