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):
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.0const 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.0wh = novavms.webhooks.create( name="SOC alerts", url="https://hooks.example.com/novavms", event_types=["alert", "event"], enabled=True,)print(wh.secret) # shown onceExpected 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):
| Header | Example | Purpose |
|---|---|---|
X-Webhook-ID | a9f3c1e2-0000-4000-8000-000000000001 | Unique per delivery; use for idempotency |
X-Webhook-Timestamp | 2026-04-21T14:05:00Z | ISO 8601; reject deliveries > 5 min old to prevent replay |
X-Webhook-Event | alert | One of alert, event, camera_status, gateway_status |
X-Webhook-Signature | 3a8f... (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.
Related
- Verify webhook signatures — HMAC-SHA256 verification
- Webhook event types — schema per event type
- Create and rotate webhooks — admin UI flow