{
"id": "evt_01JA2ZD4N5P6Q7R8S9T0VWXYZ4",
"type": "endpoint.verification",
"created_at": "2026-10-14T08:00:00.000Z",
"data": {
"endpoint_id": "whe_01JA2Z0A1B2C3D4E5F6G7H8J9K",
"challenge": "q3Jt8mV0x2Yb7Lk9Pz4Rw6Hn1Sd5Fg8Ja0Tc3Ue7Wi4"
}
}{
"challenge": "q3Jt8mV0x2Yb7Lk9Pz4Rw6Hn1Sd5Fg8Ja0Tc3Ue7Wi4"
}Endpoint verification
Sent when you ask for it with POST /v1/webhooks/{endpoint_id}/test, before any assessment event reaches the endpoint. Verify the signature, then answer 200 with Content-Type: application/json and the body {"challenge": "<the same value>"} within 5 seconds.
Anything else leaves the endpoint unverified, and no assessment event is sent to it.
{
"id": "evt_01JA2ZD4N5P6Q7R8S9T0VWXYZ4",
"type": "endpoint.verification",
"created_at": "2026-10-14T08:00:00.000Z",
"data": {
"endpoint_id": "whe_01JA2Z0A1B2C3D4E5F6G7H8J9K",
"challenge": "q3Jt8mV0x2Yb7Lk9Pz4Rw6Hn1Sd5Fg8Ja0Tc3Ue7Wi4"
}
}{
"challenge": "q3Jt8mV0x2Yb7Lk9Pz4Rw6Hn1Sd5Fg8Ja0Tc3Ue7Wi4"
}Headers
The id of the body; equal across redeliveries and every endpoint the event fans out to. Opaque, workspace-scoped webhook event id. A redelivery of the same event carries the same event_id, so consumers deduplicate on it.
^evt_[0-9A-HJKMNP-TV-Z]{26}$Unix time in whole seconds, decimal, of this delivery attempt. Signed; outside 300 seconds of the receiver's clock the delivery is refused.
^[0-9]{1,12}$Names the per-workspace signing secret used, so a receiver holding two secrets during rotation picks the right one.
^whk_[A-Za-z0-9]{8,64}$'v1=' and the lowercase hex HMAC-SHA256 of NR-Webhook-Timestamp + '.' + the raw body.
^v1=[0-9a-f]{64}$Body
Body of every webhook delivery: {id, type, created_at, data}, signed as described in this reference (NR-Webhook-Signature over 'timestamp.payload'). Six event types. assessment.updated fires when an assessment's score, band, recommendation, coverage or status changes while it is served; updates that change none of these are coalesced, and the final one has status complete. An expiry is terminal and carries only minimal data: status 'expired', change_reason 'expired', the last served assessment_version and no assessment (GET now answers 404); the consumer deletes what it holds for the assessment. account.deleted fires once when DELETE /v1/accounts/{account_id} erased at least one assessment of the account: its data names the account_id, how many assessments went and when; the consumer deletes what it holds from the account's assessments created at or before data.deleted_at, ignores a later-arriving update of such an assessment, and keeps any assessment created after it (the account assessed again), even when the event arrives last. A repeat erasure that deleted nothing sends no event. workspace.deleted is the last event an environment's endpoints receive: it fires once per environment when its workspace is deleted (DELETE /v1/workspaces/{workspace_id}), after the deletion took effect (every key answers 401, every assessment and account is gone) and before the endpoints themselves are removed; the consumer deletes what it holds for that environment and stops calling the API with its keys. webhook_endpoint.disabled fires when one of the environment's endpoints was disabled after failing continuously for 72 hours: it goes to the environment's other verified endpoints (the disabled endpoint receives nothing until it is verified again), and is not sent when there are none. account.risk_changed fires once when the decision of an account for at least one flow changes: its data is the account as GET /v1/accounts/{account_id} answers it for the revision that made the change (the new score, decisions and reasons) and the decisions per flow it replaced; a score that moves without changing any flow's decision sends nothing, and an account's first risk sends nothing (the assessment answer carries it). An account's changes are fanned out in the order they happened. endpoint.verification carries a challenge the endpoint must echo to prove it is the intended receiver. Deliveries are at-least-once and unordered: deduplicate on id, and drop an assessment whose detail.assessment_version is not newer than the one held. Polling GET /v1/assessments/{assessment_id} stays authoritative. An event sent by POST /v1/webhooks/{endpoint_id}/test carries test: true and a fixed example assessment.
The event id, equal to the NR-Webhook-Id header and across redeliveries and every endpoint the event fans out to.
^evt_[0-9A-HJKMNP-TV-Z]{26}$assessment.updated, endpoint.verification, account.deleted, workspace.deleted, webhook_endpoint.disabled, account.risk_changed When the event was created. Equal across redeliveries; the signing timestamp header is per delivery.
Shape set by type: the assessment resource (the assessment schema) or $defs/assessment_expired for assessment.updated, $defs/endpoint_verification for endpoint.verification, $defs/account_deleted for account.deleted, $defs/workspace_deleted for workspace.deleted, $defs/webhook_endpoint_disabled for webhook_endpoint.disabled, $defs/account_risk_changed for account.risk_changed.
Present, and true, only on an event sent by POST /v1/webhooks/{endpoint_id}/test: its data is a fixed example assessment, not one of the workspace's, so a receiver acknowledges it and applies nothing. Real events have no test member.
Response
The challenge, echoed. Required for verification.
The body an endpoint returns, with status 200 and Content-Type application/json within 5 seconds, to an endpoint.verification event. A 2xx without the echoed challenge does not verify the endpoint.
Endpoint verification nonce: 32 random bytes, unpadded base64url (43 characters). Fresh per verification.
^[A-Za-z0-9_-]{43}$