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 | e2d5b3a1-9876-4321-abcd-0123456789ab | Webhook 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-Timestamp | 2026-04-21T14:05:00Z | RFC 3339 UTC. 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, 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.
Related
- Verify webhook signatures — HMAC-SHA256 verification
- Webhook event types — schema per event type
- Create and rotate webhooks — admin UI flow