Skip to Content
Welcome to the Novantra documentation.

Webhooks

Webhooks deliver outbound notifications from Novantra to your integration when important state changes happen in your workspace. They let you avoid polling list endpoints just to discover new findings, completed assessments, or submission events.

Event catalogue

The v1 webhook surface covers the events external integrations most often need:

EventWhen it fires
finding.createdA new finding was recorded in your workspace (via the UI or POST /api/v1/governance/findings).
finding.updatedA finding’s severity, status, owner, or due date changed.
finding.closedA finding was closed (resolved, accepted, or invalidated).
evidence.claim.createdA new evidence claim was created.
evidence.claim.approvedA previously-pending evidence claim was approved by a reviewer.
evidence.claim.rejectedA previously-pending evidence claim was rejected by a reviewer.
submission.package.status_changedA submission package moved through its lifecycle (e.g., from draft to submitted, or from submitted to accepted).
submission.package.event_recordedA submission event was recorded against a package (receipt, rejection, withdrawal, resubmission).
assessment.completedAn assessment instance finished and its results are available.

The catalogue above is the stable event set for v1.

Payload shape

Every webhook delivery is a POST to your configured URL with this JSON body:

{ "id": "evt_01HXY...", "type": "finding.created", "createdAt": "2026-05-21T12:34:56.789Z", "organization": { "id": "org_01H...", "slug": "acme" }, "data": { "finding": { "id": "find_01H...", "title": "Annual control review overdue", "severity": "medium", "status": "open", "subjectModuleKey": "controls", "subjectResourceType": "control", "subjectResourceId": "ctrl_01H...", "createdAt": "2026-05-21T12:34:56.123Z" } } }

Fields you can rely on across every event type:

  • id - unique delivery ID. Use for deduplication.
  • type - the event type. Branch on this.
  • createdAt - when the event happened in the workspace.
  • organization - which workspace it came from (always set).
  • data - the event-specific payload. The shape inside data matches the public resource shape for that event type.

Signature verification

Every webhook delivery must be verified. Without verification, an attacker who learns your webhook URL could impersonate Novantra and forge events.

Each delivery includes:

X-Novantra-Signature: t=1716321296,v1=<signature> X-Novantra-Timestamp: 2026-05-21T12:34:56.789Z

To verify:

  1. Concatenate the timestamp and the raw request body with a . separator: <timestamp>.<body>.
  2. Compute HMAC-SHA256 of that string, using your webhook’s signing secret as the key.
  3. Compare the hex digest to the v1= value in the X-Novantra-Signature header. Use a constant-time comparison.
  4. Reject the delivery if the digest doesn’t match.
  5. Reject the delivery if the timestamp is older than 5 minutes (replay defense).

Pseudocode:

def verify(headers, raw_body, signing_secret): signature = parse_v1(headers["X-Novantra-Signature"]) timestamp = headers["X-Novantra-Timestamp"] if not within_5_minutes(timestamp): return False expected = hmac.new( signing_secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature)

Compute the HMAC over the raw request body, exactly as sent. Re-serializing the JSON before hashing breaks verification because key order and whitespace matter.

Subscribing

Create a webhook subscription for a service account from Developers -> Webhooks -> Endpoints, or programmatically with a token that has the webhooks:manage scope:

POST /api/v1/webhooks Authorization: Bearer <access-token> Content-Type: application/json { "url": "https://example.com/novantra/webhook", "events": ["finding.created", "finding.closed", "submission.package.status_changed"], "description": "ServiceNow incident sync", "reason": "Wire findings into ServiceNow incident queue." }

Response:

{ "webhook": { "id": "whk_01H...", "url": "https://example.com/novantra/webhook", "events": ["finding.created", "finding.closed", "submission.package.status_changed"], "status": "active", "createdAt": "2026-05-21T12:34:56.789Z", "signingSecret": "<webhook-signing-secret>" } }

The signingSecret is returned only once, at creation time. Store it in your secrets store. You can rotate it later, but you cannot retrieve it.

You may subscribe to specific events (as above) or to * to receive every event type your token has read scope for.

Delivery semantics

  • At-least-once. A given event may be delivered more than once if your endpoint is briefly unavailable or returns a non-2xx status. Use the delivery id to deduplicate.
  • Ordering is best-effort within an event type. Cross-type ordering is not guaranteed.
  • Timeouts. Your endpoint must respond within 10 seconds with a 2xx status. Otherwise the delivery is considered failed and queued for retry.
  • Synchronous vs asynchronous. Acknowledge with 2xx first, then process. Long-running processing should happen in a background queue, not in the request handler.

Retries

Failed deliveries are retried with exponential backoff:

AttemptDelay
1 (initial)immediate
230 seconds
32 minutes
410 minutes
51 hour
66 hours
724 hours

After 7 failed attempts, the delivery is marked dead-lettered. The webhook itself is not disabled, but the workspace surface shows a delivery failure that an admin can review and trigger manual replay.

Replay

Workspace admins can replay any past delivery from the webhook detail page.

A replayed delivery uses the same payload as the original; only the delivery id changes. Your deduplication should key on event id (which is stable across replays) rather than delivery id.

Disabling and rotating

  • Pause a webhook to temporarily stop deliveries without losing the subscription. New events accumulate and are delivered when the webhook is unpaused.
  • Rotate signing secret to invalidate the current secret. A grace window of 60 seconds keeps the old secret valid so you can update your integration’s stored secret without a window of failed verifications.
  • Delete a webhook to permanently end the subscription. Any pending deliveries are dropped.

What is and isn’t a webhook event

Webhook event?
Customer-impacting state changes (find created, evidence approved, etc.)yes
Background system events (job runs, posture sweeps)no
Admin-only events (license renewals, mailer config changes)no
Per-field updates inside a resourceno - one *.updated event covers any field change
Readsno - webhooks are write/state-change-driven only

Compliance and audit

Every webhook delivery is recorded in the workspace audit log with its delivery ID, the event ID, and the destination URL. Webhook subscription changes (create, update, delete, rotate secret, pause) are also audited.

Next

Last updated on