API webhooks
Noxtica sends signed HTTPS requests, called deliveries, to the endpoints you register in the Backoffice under Integrations & API, Webhooks, or with the Webhook endpoints API. A delivery tells your system that something happened. Treat it as a notification and read the current state from the API before you act on it.
The signing secret of an endpoint is shown once when the endpoint is created or rotated. Store it like an API key.
Delivery request
Each delivery is a POST with a JSON body. These headers accompany it:
| Header | Required | Description |
|---|---|---|
X-Noxtica-Signature | Yes | sha256= followed by the lowercase hex HMAC-SHA256 of the string <X-Noxtica-Timestamp>.<raw request body>, keyed with the endpoint’s signing secret (the secret string’s UTF-8 bytes). Verify against the raw body bytes before parsing, with a constant-time comparison. |
X-Noxtica-Timestamp | Yes | Unix time in milliseconds at which this attempt was signed. Every retry is re-signed with a fresh timestamp, so compare it with your own clock (a 300 second tolerance is recommended; Noxtica itself enforces none), not with the body’s created_at. |
X-Noxtica-Event | Yes | The event type; equals type in the body. |
X-Noxtica-Delivery | Yes | The delivery id; equals id in the body and data.deliveryId. It is identical on every retry of the same delivery: use it as the deduplication key. |
Every delivery body is a JSON object with the top-level fields id, type, created_at, tenant_id, data. The content of data depends on the event and is described with each event below.
Verify the signature
The signature header holds sha256= followed by the lowercase hexadecimal HMAC-SHA256 of the string <timestamp header>.<raw request body>, keyed with the signing secret of the endpoint. Compute it over the raw body bytes before you parse the JSON, compare it in constant time, and reject a delivery whose timestamp is outside a tolerance you choose. The samples use five minutes. The timestamp is in milliseconds since the Unix epoch.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_MS = 5 * 60 * 1000;
export function verifyWebhook({ secret, rawBody, signature, timestamp }) {
const age = Math.abs(Date.now() - Number(timestamp));
if (!Number.isFinite(age) || age > TOLERANCE_MS) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
const received = String(signature).replace(/^sha256=/, '').toLowerCase();
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && timingSafeEqual(a, b);
}
Python
import hashlib
import hmac
import time
TOLERANCE_MS = 5 * 60 * 1000
def verify_webhook(secret: str, raw_body: bytes, signature: str, timestamp: str) -> bool:
try:
sent = int(timestamp)
except ValueError:
return False
if abs(time.time() * 1000 - sent) > TOLERANCE_MS:
return False
signed = timestamp.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
received = signature.removeprefix("sha256=").lower()
return hmac.compare_digest(expected, received)
Shell (openssl)
printf '%s.%s' "$TIMESTAMP" "$RAW_BODY" \
| openssl dgst -sha256 -hmac "$WEBHOOK_SECRET"
Compare the printed hexadecimal digest with the part of the signature header after sha256=.
Delivery, retries and deduplication
Answer every delivery with a 2xx status as soon as you have stored it, and process it afterwards. The delivery header is the same on every retry of one delivery, so use it as the deduplication key.
Noxtica sends events to the HTTPS endpoints your organisation registers in the Backoffice (Integrations & API, Webhooks); an endpoint receives only the event types ticked on it. An endpoint URL must be HTTPS without embedded credentials and must not point at a private, loopback or internal address, and an organisation can register at most 25 endpoints. The signing secret is shown once when the endpoint is created or rotated; a rotation invalidates the old secret at once.
Request. Each delivery is POST with Content-Type: application/json and the headers
X-Noxtica-Signature, X-Noxtica-Timestamp (Unix milliseconds), X-Noxtica-Event and
X-Noxtica-Delivery. The body is the envelope {"id","type","created_at","tenant_id","data"}:
id equals X-Noxtica-Delivery, created_at is Unix milliseconds and tenant_id is your
organisation. Verdict payloads carry no raw IP address and no personal data.
Signature. X-Noxtica-Signature is sha256= plus the lowercase hex HMAC-SHA256, keyed with
the endpoint’s secret (its UTF-8 bytes), of the string <X-Noxtica-Timestamp>.<raw body>.
Recompute it over the raw body bytes before parsing the JSON, compare in constant time, and
reject a stale timestamp (Noxtica enforces no tolerance of its own; 300 seconds is
recommended). Every retry is re-signed with a fresh timestamp.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, timestamp, rawBody, signatureHeader) {
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const given = signatureHeader.replace(/^sha256=/, '').toLowerCase();
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
Delivery and retries. Delivery is at-least-once, never exactly-once: deduplicate on
X-Noxtica-Delivery, which is identical on every retry of the same delivery. Answer within
5 seconds; redirects are never followed. Any 2xx marks the delivery delivered. Any 4xx
(including 408 and 429) is final: it is never retried. A 3xx, a 5xx, a timeout or a
network error is retried, up to 5 retries after the first attempt, after which the delivery is
recorded as dead and is not retried again. The spacing between retries is not published.
Respond 2xx first and process asynchronously. Events can arrive out of order. A webhook is a
notification, not authority: read the resource it refers to before taking an irreversible
action. POST /v1/webhooks/{webhookId}/test and the Backoffice “Send test” button send a signed sample of any event.
Event catalog
| Event | Sent when | API |
|---|---|---|
kyc.session.completed | A hosted verification session reached a business outcome | Identity verification API |
kyc.session.cancelled | The end customer cancelled a hosted verification session | Identity verification API |
kyc.verification.completed | A KYC verification request reached a terminal state | Identity verification API |
kyc.erasure.completed | A KYC erasure request finished successfully | Identity verification API |
kyc.document.expiry_warning | A verified identity document is approaching or past its expiry | Identity verification API |
kyc.reverification.due | A previously verified identity is due for re-verification | Identity verification API |
kyc.session.created | A hosted verification session (invitation) was created | Identity verification API |
kyc.session.opened | The customer opened a hosted verification session | Identity verification API |
kyc.session.verifying | A hosted verification session started verifying | Identity verification API |
kyc.session.review_pending | A hosted verification session is waiting for a manual review | Identity verification API |
kyc.session.unverified | A hosted verification session ended unverified | Identity verification API |
kyc.session.error | A hosted verification session ended with an error | Identity verification API |
kyc.session.expired | A hosted verification session expired | Identity verification API |
kyc.review.decided | A reviewer approved or overrode a verification | Identity verification API |
submission.scored | A submission was scored | Device intelligence API |
risk.high | A submission was scored high or critical | Device intelligence API |
risk.flagged | A risk flag fired | Device intelligence API |
risk.cluster_flagged | A device cluster was flagged as bad | Device intelligence API |
account.agent_linked | A verified agent was linked to an account for the first time | Device intelligence API |
kyc.session.completed
A hosted verification session reached a business outcome
Sent by Noxtica to every endpoint you registered for kyc.session.completed
(Backoffice, Integrations & API, Webhooks). See “Webhooks (KYC)” in the API description
for the headers, the signature, retries and deduplication; they are common to all six
KYC events.
When it fires. A hosted session reached completed, unverified or error.
It does not fire when a session lands in review_pending. With the identity provider
configured today, a successful hosted capture ends in review_pending (the provider
routes every success to manual review, reason code provider_manual_review); the event
is sent later, when a Backoffice reviewer approves or overrides the case and the session
moves review_pending to completed. The platform sends it right after the decision and
retries on its 5-minute tick, so allow up to about 5 minutes between the reviewer’s
decision and the delivery. A review_pending session never expires, so your system must
keep waiting for the decision (or poll GET /v1/kyc/sessions/{sessionId}). It never fires
for expired or cancelled_by_user sessions.
status: completed means a reviewer decided; it does not mean the person is verified,
because an override completes the session too. The payload does not say which decision
was made: read verification and review on GET /v1/kyc/sessions/{sessionId}.
This is a notification, not authority: before you act on it, read
GET /v1/kyc/sessions/{sessionId}. The payload carries no subject reference; correlate
by data.sessionId, the id you stored when you minted the session.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.completed. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionCompletedWebhookData) | Yes | The data member of kyc.session.completed (schemaVersion "1"). Delivery is at-least-once: deduplicate by deliveryId. A webhook is a notification, not authority; read GET /v1/kyc/sessions/{sessionId} for the outcome. There is no subject reference, digest, evidence id or scorecard. The event does not fire when a session lands in review_pending; it fires when a reviewer’s decision moves it to completed. email and emailVerified (added 2026-10-01, additive) are present only when hosted email verification is enabled for the platform AND the session has a stored address; otherwise both keys are absent. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The terminal session status. expired and cancelled_by_user sessions never emit this event. Allowed values: completed, unverified, error. |
data.verificationId | string, nullable | Yes | The verification the submit ran (also on the status read); null when none ran. Format and limits are in KycVerificationId. |
data.reasonCodes | array of string | Yes | |
data.occurredAt | integer (int64) | Yes | Unix ms the session reached this status (its completedAt). |
data.deliveryId | string | Yes | |
data.nvid | string | No | The session’s Noxtica Verification ID (NVID-XXXX-XXXX-XXXX-XXXX). Present only when the session has one (the feature is enabled for your organisation and the outcome was positive); otherwise absent. Never accompanied by the address the person may have given to receive it. |
data.email | string | No | Present only when the session has a stored address (and hosted email verification is enabled for the platform). The normalised address the end customer entered in the hosted flow. Unverified unless emailVerified is true. |
data.emailVerified | boolean | No | Present together with email. true only if the customer clicked the confirmation link for email. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c",
"type": "kyc.session.completed",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "completed",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reasonCodes": [],
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c"
}
}
kyc.session.cancelled
The end customer cancelled a hosted verification session
Sent when the customer confirms on the hosted page that they do not want to continue and
the session actually moves to cancelled_by_user. Available only when session
cancellation is enabled for your organisation. Subscriptions are opt-in per event: an
endpoint registered before this event existed does not receive it until you add it to the
endpoint’s event list.
data.cancelReason and data.cancelledAt are delivered only in this event.
GET /v1/kyc/sessions/{sessionId} reports the terminal status cancelled_by_user but does
not return the cancel reason or the cancel time. An expiry never sends this event; observe
it by reading the session status.
See “Webhooks (KYC)” in the API description for headers, signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<webhookId>. |
type | string | Yes | Allowed values: kyc.session.cancelled. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionCancelledWebhookData) | Yes | The data member of kyc.session.cancelled (schemaVersion "1"). Same delivery semantics as KycSessionCompletedWebhookData. email and emailVerified (added 2026-10-01, additive) are present only when hosted email verification is enabled for the platform AND the session has a stored address; otherwise both keys are absent. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | |
data.status | string | Yes | Allowed values: cancelled_by_user. |
data.cancelReason | one of KycSessionCancelReason or any, nullable | Yes | |
data.cancelledAt | integer (int64) | Yes | Unix ms. |
data.occurredAt | integer (int64) | Yes | Unix ms; equal to cancelledAt. |
data.deliveryId | string | Yes | |
data.email | string | No | Present only when the session has a stored address (and hosted email verification is enabled for the platform). The normalised address the end customer entered before cancelling. Unverified unless emailVerified is true. |
data.emailVerified | boolean | No | Present together with email. true only if the customer clicked the confirmation link for email. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c",
"type": "kyc.session.cancelled",
"created_at": 1790762300000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "cancelled_by_user",
"cancelReason": "not_now",
"cancelledAt": 1790762300000,
"occurredAt": 1790762300000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c"
}
}
kyc.verification.completed
A KYC verification request reached a terminal state
Sent when a verification request (POST /v1/kyc/verify, or the verification a hosted
session runs when the customer submits) reaches a terminal state: completed,
terminal_error, expired or cancelled. The non-terminal states retryable_error
and provider_unknown emit nothing. data.verdict is the stored canonical verdict
(verified, unverified or error; error for an infrastructure-faulted terminal
request that has no verdict).
A hosted session’s submit runs the same pipeline, so an endpoint subscribed to both events
can receive kyc.verification.completed for the same attempt as
kyc.session.*; with the identity provider configured today the verdict of a successful
hosted capture is unverified with the reason code provider_manual_review. Delivery
order between the two events is not guaranteed. data.verificationId equals
session.verificationId on the session status read.
See “Webhooks (KYC)” in the API description for headers, signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <verificationId>:<webhookId>. |
type | string | Yes | Allowed values: kyc.verification.completed. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycVerificationCompletedWebhookData) | Yes | The data member of kyc.verification.completed (schemaVersion "1"). A closed, non-personal field set: no subject id, digest or document value. Deduplicate by deliveryId; read the verification through the API before acting. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.verificationId | string | Yes | The verification. Format and limits of a verification id are in KycVerificationId. |
data.verdict | string | Yes | Allowed values: verified, unverified, error. |
data.reasonCodes | array of string | Yes | Closed reason-code catalog; may be empty. |
data.idType | string | Yes | The document type asserted on the request, e.g. passport. |
data.occurredAt | integer (int64) | Yes | Unix ms the verification reached its terminal state. |
data.deliveryId | string | Yes |
Example payload
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:wh_7f3a2c",
"type": "kyc.verification.completed",
"created_at": 1790762440000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"verdict": "unverified",
"reasonCodes": [
"provider_manual_review"
],
"idType": "passport",
"occurredAt": 1790762440000,
"deliveryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6:wh_7f3a2c"
}
}
kyc.erasure.completed
A KYC erasure request finished successfully
Sent when an erasure request reaches completed. A partial erasure never emits this
event. The payload deliberately carries no subject identifier or subject digest: an erasure
notification must not re-establish a link to the person whose linkage was just erased.
data.requestId is the erasure request’s own id.
See “Webhooks (KYC)” in the API description for headers, signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals data.deliveryId. |
type | string | Yes | Allowed values: kyc.erasure.completed. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycErasureCompletedWebhookData) | Yes | The data member of kyc.erasure.completed (schemaVersion "1"). Emitted only when the erasure request reaches completed; a partial erasure emits nothing. No subject identifier is carried. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.requestId | string | Yes | The erasure request’s own id; not a subject id. |
data.outcome | string | Yes | Allowed values: completed. |
data.occurredAt | integer (int64) | Yes | Unix ms the erasure request reached its terminal state. |
data.deliveryId | string | Yes |
Example payload
{
"id": "er_4b81d2:wh_7f3a2c",
"type": "kyc.erasure.completed",
"created_at": 1790900000000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"requestId": "er_4b81d2",
"outcome": "completed",
"occurredAt": 1790900000000,
"deliveryId": "er_4b81d2:wh_7f3a2c"
}
}
kyc.document.expiry_warning
A verified identity document is approaching or past its expiry
Sent by the scheduled document-expiry monitoring pass when a monitored verification’s
document crosses the 90, 60 or 30 day threshold or has expired (data.alertType). Only
available when document-expiry monitoring is enabled for your organisation. Each
threshold fires once per monitored verification.
Unlike the other KYC events, this event is not read from the delivery ledger: the
scheduled pass makes a single signed delivery attempt to each subscribed endpoint and
does not retry a failed attempt. Deduplicate on the delivery id anyway, and use
data.verificationId to correlate with the original kyc.verification.completed.
See “Webhooks (KYC)” for headers and signature.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals data.deliveryId. |
type | string | Yes | Allowed values: kyc.document.expiry_warning. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycDocumentExpiryWarningWebhookData) | Yes | The data member of kyc.document.expiry_warning (schemaVersion "1"). No subject id and no document number: correlate by verificationId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.verificationId | string | Yes | The verification. Format and limits of a verification id are in KycVerificationId. |
data.alertType | string | Yes | Allowed values: expiry_90d, expiry_60d, expiry_30d, expiry_expired. |
data.documentExpiresAt | integer (int64) | Yes | Unix ms the monitored document expires (or expired). |
data.occurredAt | integer (int64) | Yes | Unix ms the threshold crossing was detected. |
data.deliveryId | string | Yes |
Example payload
{
"id": "sched_91c0:wh_7f3a2c",
"type": "kyc.document.expiry_warning",
"created_at": 1798538000000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"alertType": "expiry_30d",
"documentExpiresAt": 1801130000000,
"occurredAt": 1798538000000,
"deliveryId": "sched_91c0:wh_7f3a2c"
}
}
kyc.reverification.due
A previously verified identity is due for re-verification
Sent by the same scheduled monitoring pass as kyc.document.expiry_warning when a
re-verification schedule entry reaches its due time. Only available when document-expiry
monitoring is enabled for your organisation, and only for a verification that has a
re-verification schedule entry. Single signed delivery attempt, no retry, as for
kyc.document.expiry_warning.
See “Webhooks (KYC)” in the API description for headers and signature.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals data.deliveryId. |
type | string | Yes | Allowed values: kyc.reverification.due. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycReverificationDueWebhookData) | Yes | The data member of kyc.reverification.due (schemaVersion "1"). No subject id. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.verificationId | string | Yes | The verification. Format and limits of a verification id are in KycVerificationId. |
data.dueAt | integer (int64) | Yes | Unix ms the re-verification became due. |
data.occurredAt | integer (int64) | Yes | |
data.deliveryId | string | Yes |
Example payload
{
"id": "sched_92d1:wh_7f3a2c",
"type": "kyc.reverification.due",
"created_at": 1801130000000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"dueAt": 1801130000000,
"occurredAt": 1801130000000,
"deliveryId": "sched_92d1:wh_7f3a2c"
}
}
kyc.session.created
A hosted verification session (invitation) was created
Sent to every endpoint subscribed to kyc.session.created when a hosted KYC session is created: by POST /v1/kyc/sessions or by an invitation in the Backoffice. data.previousStatus is null.
data.status is pending and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.created:<webhookId> and is identical on every retry.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.created:wh_7f3a2c",
"type": "kyc.session.created",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "pending",
"previousStatus": null,
"verificationId": null,
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.created:wh_7f3a2c"
}
}
kyc.session.opened
The customer opened a hosted verification session
Sent to every endpoint subscribed to kyc.session.opened when the customer opens the hosted link and the session leaves pending.
data.status is opened and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.opened:<webhookId> and is identical on every retry.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.opened:wh_7f3a2c",
"type": "kyc.session.opened",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "opened",
"previousStatus": "pending",
"verificationId": null,
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.opened:wh_7f3a2c"
}
}
kyc.session.verifying
A hosted verification session started verifying
Sent to every endpoint subscribed to kyc.session.verifying when the customer has submitted and the verification is running. The outcome follows as another event.
data.status is verifying and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.verifying:<webhookId> and is identical on every retry.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.verifying:wh_7f3a2c",
"type": "kyc.session.verifying",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "verifying",
"previousStatus": "opened",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.verifying:wh_7f3a2c"
}
}
kyc.session.review_pending
A hosted verification session is waiting for a manual review
Sent to every endpoint subscribed to kyc.session.review_pending when the verification needs a decision by a Backoffice reviewer before the session can finish.
data.status is review_pending and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.review_pending:<webhookId> and is identical on every retry.
A review_pending session never expires. kyc.session.completed follows when a reviewer approves or overrides the case; kyc.review.decided announces the decision itself.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.review_pending:wh_7f3a2c",
"type": "kyc.session.review_pending",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "review_pending",
"previousStatus": "verifying",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reasonCodes": [
"provider_manual_review"
],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.review_pending:wh_7f3a2c"
}
}
kyc.session.unverified
A hosted verification session ended unverified
Sent to every endpoint subscribed to kyc.session.unverified when the session ends unverified. data.reasonCodes lists the closed reason codes.
data.status is unverified and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.unverified:<webhookId> and is identical on every retry.
kyc.session.completed also announces this outcome (it announces every terminal outcome: completed, unverified and error); subscribe to this event instead of it when you only care about the narrower case.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.unverified:wh_7f3a2c",
"type": "kyc.session.unverified",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "unverified",
"previousStatus": "verifying",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.unverified:wh_7f3a2c"
}
}
kyc.session.error
A hosted verification session ended with an error
Sent to every endpoint subscribed to kyc.session.error when the session ends with a verification error.
data.status is error and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.error:<webhookId> and is identical on every retry.
kyc.session.completed also announces this outcome; subscribe to this event instead of it when you only care about the narrower case.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.error:wh_7f3a2c",
"type": "kyc.session.error",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "error",
"previousStatus": "verifying",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.error:wh_7f3a2c"
}
}
kyc.session.expired
A hosted verification session expired
Sent to every endpoint subscribed to kyc.session.expired when a session lapsed before it reached an outcome (the unopened link expired, or the capture window passed). The event is sent when the expiry is recorded, which happens when the session is next used or when the platform’s expiry sweep runs, so it can follow the moment the session lapsed by some time; the status read always shows the current state.
data.status is expired and data.previousStatus names the status the session left (null when there is none). Correlate by data.sessionId, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint’s event list. The delivery id is <sessionId>:kyc.session.expired:<webhookId> and is identical on every retry.
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read GET /v1/kyc/sessions/{sessionId}.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.session.created, kyc.session.opened, kyc.session.verifying, kyc.session.review_pending, kyc.session.unverified, kyc.session.error, kyc.session.expired. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycSessionLifecycleWebhookData) | Yes | The data member of every kyc.session.created, .opened, .verifying, .review_pending, .unverified, .error and .expired event (schemaVersion "1"). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.sessionId | string (uuid) | Yes | The id the mint call returned. |
data.status | string | Yes | The status the session entered; the event type names it. Allowed values: pending, opened, verifying, review_pending, unverified, error, expired. |
data.previousStatus | string, nullable | Yes | The status the session left, or null when there is none (the created event, or a transition that was not recorded). Allowed values: pending, opened, verifying, review_pending, unverified, error, expired, completed, cancelled_by_user, null. |
data.verificationId | string, nullable | Yes | The verification id once a verification ran |
data.reasonCodes | array of string | Yes | Closed reason codes for this status (empty for the non-terminal statuses). Never free text. |
data.domainId | string, nullable | Yes | The domain the invitation was created for (d_...) |
data.occurredAt | integer (int64) | Yes | Unix ms the session entered the status. |
data.deliveryId | string | Yes | The delivery id, <sessionId>:<event type>:<webhookId>. Identical on every retry; equals X-Noxtica-Delivery. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.expired:wh_7f3a2c",
"type": "kyc.session.expired",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"status": "expired",
"previousStatus": "opened",
"verificationId": null,
"reasonCodes": [],
"domainId": null,
"occurredAt": 1790762445000,
"deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.expired:wh_7f3a2c"
}
}
kyc.review.decided
A reviewer approved or overrode a verification
Sent to every endpoint subscribed to kyc.review.decided when a Backoffice reviewer, or your own back office through POST /v1/kyc/verifications/{verificationId}/review-decision, writes an affirmative decision, approve or override, for a verification. A changed decision is a new delivery: data.decisionVersion increases with every written decision, and the delivery id is <verificationId>:kyc.review.decided:<decisionVersion>:<webhookId>.
This is the event of the decision itself. It is sent for a hosted session and for a verification you created through the API, which has no session to complete. An escalation (review state in_review, no decision) sends nothing. The event is written in the same transaction as the decision and sent right after it, with the platform’s 5-minute tick as the durable retry path, so allow up to about 5 minutes.
data.decision is approve or override. Only approve makes the verification verified (verification.source is then review); an override does not, and there is no reject decision today. data.reviewState is the state of the review after the decision: approved for approve, overridden for override. data.sessionId is the hosted session the verification belongs to, or null for a verification you created through the API. data.actor.type says who wrote the decision: panel (a Backoffice reviewer) or api (your own back office through the review decision route); a decision from either source has the same payload shape, the same decisionVersion counter and the same delivery id format. data.reasonCodes holds the closed reason codes a Backoffice reviewer attached (an empty array for an api decision), never free text. data.reviewReasonCode is the closed reason code of an api decision (for approve: identity_confirmed, registry_crosscheck_passed, name_variant_accepted, manual_document_check_passed or other; for override: identity_mismatch, registry_crosscheck_failed, document_invalid, suspected_fraud, liveness_not_accepted or other) and null for a panel decision. A delivery created before this member existed carries neither actor nor reviewReasonCode: read an absent actor as panel. The payload never carries the reviewer, the API key or the note.
For a hosted session that was review_pending, kyc.session.completed follows or arrives alongside, as a separate delivery with no guaranteed order. kyc.verification.completed is not sent again after a review. After either event, read the current outcome: verification and review on GET /v1/kyc/sessions/{sessionId} (hosted session), or on GET /v1/kyc/verifications/{verificationId}/identity (any verification; needs the read:identity_details scope).
See “Webhooks” in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: read the verification before you act on it.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery and data.deliveryId. |
type | string | Yes | Allowed values: kyc.review.decided. |
created_at | integer (int64) | Yes | Unix ms the event occurred. |
tenant_id | string | Yes | |
data | object (KycReviewDecidedWebhookData) | Yes | The data member of kyc.review.decided (schemaVersion "1"). No reviewer identity, no API key and no note. Deduplicate by deliveryId. |
data.schemaVersion | string | Yes | Allowed values: 1. |
data.verificationId | string | Yes | The reviewed verification. Format and limits of a verification id are in KycVerificationId. |
data.sessionId | string, nullable | Yes | The hosted session the verification belongs to |
data.decision | string | Yes | Allowed values: approve, override. |
data.reviewState | string | Yes | The review state after the decision: approved for approve, overridden for override. |
data.decisionVersion | integer | Yes | The review version after the decision: 1 for the first review write of the verification, then one more with every write (an escalation counts). A changed decision is a new delivery. Minimum 0. |
data.reasonCodes | array of string | Yes | Closed reason codes the reviewer attached. Never free text. |
data.actor | object | No | Who wrote the decision. Absent on a delivery created before this member existed; read an absent actor as panel. |
data.actor.type | string | Yes | panel: a Backoffice reviewer wrote the decision. api: your own back office wrote it through POST /v1/kyc/verifications/{verificationId}/review-decision. Allowed values: panel, api. |
data.reviewReasonCode | string, nullable | No | The closed reason code of an api decision (see KycReviewDecisionReasonCode); null for a panel decision. Absent exactly when actor is absent. |
data.occurredAt | integer (int64) | Yes | Unix ms the decision was written. |
data.deliveryId | string | Yes |
Example payload
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:kyc.review.decided:1:wh_7f3a2c",
"type": "kyc.review.decided",
"created_at": 1790762445000,
"tenant_id": "tenant_42",
"data": {
"schemaVersion": "1",
"verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"decision": "approve",
"reviewState": "approved",
"decisionVersion": 1,
"reasonCodes": [],
"actor": {
"type": "panel"
},
"reviewReasonCode": null,
"occurredAt": 1790762445000,
"deliveryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6:kyc.review.decided:1:wh_7f3a2c"
}
}
submission.scored
A submission was scored
Sent for every scored browser or app submission of your organisation (the broadest
subscription: use risk.high or risk.flagged if you only need the notable ones). The
Backoffice “Send test” button sends a sample of this event type, with placeholder verdict
data and a valid signature.
data.event_id is the request id of the submission: correlate it with the X-Request-Id
your browser integration received. data.device_id is the opaque device id
(fingerprintId). This is a notification, not authority; read the device
(GET /v1/device/{deviceId}) before taking an irreversible action.
See “Webhook delivery and retries” in the API description for the headers, the signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery. |
type | string | Yes | Allowed values: submission.scored, risk.high, risk.flagged, risk.cluster_flagged. |
created_at | integer (int64) | Yes | Unix ms the event was produced. |
tenant_id | string | Yes | |
data | object (VerdictWebhookData) | Yes | The verdict of one scored submission. Never carries a raw IP address or personal data. |
data.score | number | No | The numeric risk score. |
data.risk_level | string | No | The verdict band, for example low, medium, high or critical. |
data.confidence | number | No | Model confidence from 0 to 1. |
data.flags | array of string | No | The risk flags that fired. |
data.reason_codes | array of string | No | The published reason-code projection of flags; empty unless reason-code output is enabled for your organisation. |
data.device_id | string | Yes | The opaque device id. An empty string means that no stable per-device identity is available for this event. |
data.domain_id | string | Yes | Your domain id. |
data.event_id | string | Yes | The request id of the submission or attestation; always present. |
Example payload
{
"id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
"type": "submission.scored",
"created_at": 1790762445000,
"tenant_id": "YOUR_TENANT_ID",
"data": {
"score": 12,
"risk_level": "low",
"confidence": 0.91,
"flags": [],
"reason_codes": [],
"device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"domain_id": "dom_42",
"event_id": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10"
}
}
risk.high
A submission was scored high or critical
Sent when a scored submission lands in the high or critical risk band. The payload is
the same verdict shape as submission.scored. A submission in that band also produces
submission.scored (and risk.flagged when a flag fired) for endpoints subscribed to
those types.
See “Webhook delivery and retries” in the API description for the headers, the signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery. |
type | string | Yes | Allowed values: submission.scored, risk.high, risk.flagged, risk.cluster_flagged. |
created_at | integer (int64) | Yes | Unix ms the event was produced. |
tenant_id | string | Yes | |
data | object (VerdictWebhookData) | Yes | The verdict of one scored submission. Never carries a raw IP address or personal data. |
data.score | number | No | The numeric risk score. |
data.risk_level | string | No | The verdict band, for example low, medium, high or critical. |
data.confidence | number | No | Model confidence from 0 to 1. |
data.flags | array of string | No | The risk flags that fired. |
data.reason_codes | array of string | No | The published reason-code projection of flags; empty unless reason-code output is enabled for your organisation. |
data.device_id | string | Yes | The opaque device id. An empty string means that no stable per-device identity is available for this event. |
data.domain_id | string | Yes | Your domain id. |
data.event_id | string | Yes | The request id of the submission or attestation; always present. |
Example payload
{
"id": "0b9d7b1c-2f0e-4c8b-9a52-7c1b3a1f9e11",
"type": "risk.high",
"created_at": 1790762446000,
"tenant_id": "YOUR_TENANT_ID",
"data": {
"score": 87,
"risk_level": "high",
"confidence": 0.88,
"flags": [
"example_flag"
],
"reason_codes": [],
"device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"domain_id": "dom_42",
"event_id": "3a7d1e52-6a6e-4b0e-9d52-2a7c1d9e4b11"
}
}
risk.flagged
A risk flag fired
Sent when at least one risk flag fired on a scored submission, whatever its risk band.
The same event type is also sent after each mobile attestation verify (see
POST /v1/attest/android and POST /v1/attest/ios), with flags of the form
mobile_attest:<platform>, mobile_attest_level:<level> and mobile_attest_verdict:<verdict>.
There data.device_id can be an empty string, which means that no stable per-device
identity is available for the event: do not group events on an empty device_id, and use
data.event_id, which is always present, as the per-event key.
See “Webhook delivery and retries” in the API description for the headers, the signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery. |
type | string | Yes | Allowed values: submission.scored, risk.high, risk.flagged, risk.cluster_flagged. |
created_at | integer (int64) | Yes | Unix ms the event was produced. |
tenant_id | string | Yes | |
data | object (VerdictWebhookData) | Yes | The verdict of one scored submission. Never carries a raw IP address or personal data. |
data.score | number | No | The numeric risk score. |
data.risk_level | string | No | The verdict band, for example low, medium, high or critical. |
data.confidence | number | No | Model confidence from 0 to 1. |
data.flags | array of string | No | The risk flags that fired. |
data.reason_codes | array of string | No | The published reason-code projection of flags; empty unless reason-code output is enabled for your organisation. |
data.device_id | string | Yes | The opaque device id. An empty string means that no stable per-device identity is available for this event. |
data.domain_id | string | Yes | Your domain id. |
data.event_id | string | Yes | The request id of the submission or attestation; always present. |
Example payload
{
"id": "9c1e5d2a-3b4f-4a6c-8d7e-1f2a3b4c5d6e",
"type": "risk.flagged",
"created_at": 1790762447000,
"tenant_id": "YOUR_TENANT_ID",
"data": {
"score": 40,
"risk_level": "medium",
"confidence": 0.7,
"flags": [
"example_flag"
],
"reason_codes": [],
"device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"domain_id": "dom_42",
"event_id": "5e8f2a52-6a6e-4b0e-9d52-2a7c1d9e4b12"
}
}
risk.cluster_flagged
A device cluster was flagged as bad
Sent once, on the first escalation, when a cluster of devices (devices sharing an observational sub-fingerprint) is flagged bad in real time because a high or critical submission came with strong cluster evidence. It lets you react to an emerging cluster before slower offline feeds catch up. A cluster that is already flagged does not produce a new delivery for every further submission.
The payload has the same verdict shape as submission.scored; data.risk_level is the
cluster’s band. It carries no raw IP address and no personal data. Available only when
real-time cluster propagation is enabled for the deployment.
See “Webhook delivery and retries” in the API description for the headers, the signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery. |
type | string | Yes | Allowed values: submission.scored, risk.high, risk.flagged, risk.cluster_flagged. |
created_at | integer (int64) | Yes | Unix ms the event was produced. |
tenant_id | string | Yes | |
data | object (VerdictWebhookData) | Yes | The verdict of one scored submission. Never carries a raw IP address or personal data. |
data.score | number | No | The numeric risk score. |
data.risk_level | string | No | The verdict band, for example low, medium, high or critical. |
data.confidence | number | No | Model confidence from 0 to 1. |
data.flags | array of string | No | The risk flags that fired. |
data.reason_codes | array of string | No | The published reason-code projection of flags; empty unless reason-code output is enabled for your organisation. |
data.device_id | string | Yes | The opaque device id. An empty string means that no stable per-device identity is available for this event. |
data.domain_id | string | Yes | Your domain id. |
data.event_id | string | Yes | The request id of the submission or attestation; always present. |
Example payload
{
"id": "2a4c6e8f-1b3d-4f5a-9c7e-0d2b4f6a8c1e",
"type": "risk.cluster_flagged",
"created_at": 1790762448000,
"tenant_id": "YOUR_TENANT_ID",
"data": {
"score": 91,
"risk_level": "critical",
"confidence": 0.93,
"flags": [
"example_flag"
],
"reason_codes": [],
"device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"domain_id": "dom_42",
"event_id": "7a9b1c52-6a6e-4b0e-9d52-2a7c1d9e4b13"
}
}
account.agent_linked
A verified agent was linked to an account for the first time
Sent once, when a cryptographically verified signed agent (Web Bot Auth, RFC 9421) is linked to one of your end-user accounts for the first time, that is when the (agent, account) pair had no earlier link. It lets you react when a new agent starts acting on an account.
The payload carries the keyed account_hash (a 16-character lowercase-hex pseudonym; the
raw account id is never stored or sent), the verified agent’s agent_keyid and
agent_host, and the usual device_id, domain_id and event_id. It carries no raw IP
address and no personal data. Use GET /v1/accounts/{accountHash}/devices to list the
devices and agents linked to the account.
See “Webhook delivery and retries” in the API description for the headers, the signature, retries and deduplication.
Payload
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The delivery id; equals X-Noxtica-Delivery. |
type | string | Yes | Allowed values: account.agent_linked. |
created_at | integer (int64) | Yes | Unix ms the event was produced. |
tenant_id | string | Yes | |
data | object (AgentLinkedWebhookData) | Yes | The first link between a verified agent and one of your accounts. Never carries a raw account id, IP address or personal data. |
data.account_hash | string | Yes | The keyed 16-character lowercase-hex account pseudonym. |
data.agent_keyid | string | Yes | The verified agent’s Signature-Input key id. |
data.agent_host | string, nullable | Yes | The verified agent’s directory host |
data.device_id | string | Yes | |
data.domain_id | string | Yes | |
data.event_id | string | Yes |
Example payload
{
"id": "4d6f8a1c-5e7b-4d9f-8a2c-3b5d7f9a1c2e",
"type": "account.agent_linked",
"created_at": 1790762449000,
"tenant_id": "YOUR_TENANT_ID",
"data": {
"account_hash": "0a1b2c3d4e5f6071",
"agent_keyid": "agent-key-1",
"agent_host": "agent.example.com",
"device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"domain_id": "dom_42",
"event_id": "9b1d3f52-6a6e-4b0e-9d52-2a7c1d9e4b14"
}
}
This page is generated from the Noxtica OpenAPI specification release v0.16.1: kyc.yaml version 1.14.1-2026-11-07 (SHA-256 dc9e4b53d94c), mcp.yaml version 0.1.0-2026-10-27 (SHA-256 1a4875efa736), pulse.yaml version 0.10.0-2026-10-31 (SHA-256 6864df34b799), v1.yaml version 1.5.1-2026-11-03 (SHA-256 41508500e857). Do not edit it by hand.