Receive webhooks when records change
Subscribe an endpoint to Nova events, verify the signature on every delivery, and replay what you missed.
Webhooks tell your system when something changed so you do not have to poll.
An admin opens Administration, then Webhooks under
Integrations & API (/settings/org/integrations/webhooks/endpoints),
presses Create endpoint, enters your HTTPS address, chooses the events,
and Nova shows the signing secret once. A new endpoint starts switched off:
your receiver has to pass a verification check first, then the endpoint is
switched on.
Verify the endpoint and switch it on
Verify endpoint (or POST /v1/integrations/webhooks/{endpoint_uid}/verify)
sends one request to your address with X-Nova-Webhook-Mode: verify and a
one-time token in X-Nova-Webhook-Token; the JSON body carries the same
token. Answer 2xx and echo the token back, either in an
X-Nova-Webhook-Token response header or in a JSON body:
{ "token": "<the token Nova sent>" }
Once verified, switch deliveries on. Through the API you can do both in one
call, POST …/verify with {"enable_on_success": true}, or switch on a
verified endpoint with PATCH /v1/integrations/webhooks/{endpoint_uid} and
{"enabled": true}. Switching on an endpoint that is not verified answers
400.
What a delivery looks like
Each delivery is a POST with Content-Type: application/json and these
headers:
| Header | Value |
|---|---|
X-Nova-Event |
The event name, for example crm.account.updated |
X-Nova-Canonical-Event |
The current name of the event, when you subscribed under an older alias |
X-Nova-Delivery |
Unique per delivery; use it to ignore duplicates |
X-Nova-Timestamp |
Unix seconds when the delivery was signed |
X-Nova-Signature |
t=<timestamp>,v1=<hex HMAC-SHA256> |
X-Nova-Signature-Previous |
Same format, signed with the old secret, while a rotation is in progress |
X-Nova-Occurred-At |
When the change happened (ISO 8601) |
User-Agent |
Nova-Webhooks/1.0 |
The body is an envelope around the event's data:
{
"spec_version": "2026-03-30",
"schema_version": "2026-03-30",
"organization_uid": "org_3f9a1c2b7d4e5f60",
"event_name": "crm.account.updated",
"canonical_event_name": "crm.account.updated",
"event_id": "5f2c8e1a9b3d4c7e8f6a2b1c0d9e8f7a",
"delivery_id": "0b6f5a4e-2c3d-4e5f-8a9b-1c2d3e4f5a6b",
"timestamp": "2026-09-26T14:03:12.481Z",
"occurred_at": "2026-09-26T14:03:11.902Z",
"subject": { "type": "account", "id": "acct_a1b2c3d4e5f6a1b2" },
"resource": { "type": "account", "id": "acct_a1b2c3d4e5f6a1b2" },
"data": { "uid": "acct_a1b2c3d4e5f6a1b2", "display_name": "Harper Residence" }
}
The envelope also carries scope_tier, classification, actor,
correlation_id, causation_id, ordering_key and legacy_aliases.
Records are named by their opaque id (see How records are identified). Fetch the
record through the API when you need more than data carries. A payload too
large to send whole arrives as the envelope with a marker instead of data;
fetch the record in that case.
Verify the signature
- Read
tandv1fromX-Nova-Signature. - Compute HMAC-SHA256 with your secret over
t + "." + raw_body(the exact bytes you received), hex encoded. - Compare it to
v1in constant time. During a rotation, also accept a match onX-Nova-Signature-Previous. - Reject a
tmore than five minutes old, then answer2xxquickly and do the work afterwards.
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
signed = parts["t"].encode() + b"." + body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts["v1"])
Events
The Event catalog tab on the webhooks page lists every event with a sample
payload. The same list is at GET /v1/integrations/webhooks/catalog.
Retries, dead letters and replay
A delivery that does not get a 2xx is retried with growing, jittered waits,
up to the endpoint's attempt limit (10 at most). After that it is marked
dead-lettered. An endpoint that keeps failing is paused for a cool-down and
shows as unhealthy under Endpoint health.
From Delivery history an admin can Retry one delivery or
Retry filtered, and Replay a time range after an outage. Make your
handler idempotent on X-Nova-Delivery, because a retry or a replay can send
the same change twice.
If something looks different
The webhooks page cannot open an endpoint's edit panel today (it shows "Invalid webhook link."), so switching an endpoint on, renaming it or changing its events has to go through the API calls above until that is fixed.
Rotating the secret
Press Rotate secret on the endpoint, or call
POST /v1/integrations/webhooks/{endpoint_uid}/rotate-secret. For a grace period Nova signs with
both secrets and sends the old signature in X-Nova-Signature-Previous, so
you can switch without dropping deliveries.