API webhooks
Noxtica sends signed HTTPS requests, called deliveries, to the endpoints you register in the Backoffice under Integrations & API, Webhooks. 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
- Verify the signature
- Delivery, retries and deduplication
- Event catalog
- kyc.session.completed
- kyc.session.cancelled
- kyc.verification.completed
- kyc.erasure.completed
- kyc.document.expiry_warning
- kyc.reverification.due
- submission.scored
- risk.high
- risk.flagged
- risk.cluster_flagged
- account.agent_linked
Identity verification API
6 event types
- kyc.session.completed A hosted verification session reached a business outcome
- kyc.session.cancelled The end customer cancelled a hosted verification session
- kyc.verification.completed A KYC verification request reached a terminal state
- kyc.erasure.completed A KYC erasure request finished successfully
- kyc.document.expiry_warning A verified identity document is approaching or past its expiry
- kyc.reverification.due A previously verified identity is due for re-verification
Device intelligence API
5 event types
- submission.scored A submission was scored
- risk.high A submission was scored high or critical
- risk.flagged A risk flag fired
- risk.cluster_flagged A device cluster was flagged as bad
- account.agent_linked A verified agent was linked to an account for the first time
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. The Backoffice “Send test” button sends only a sample submission.scored 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 |
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. Allow up to about 5 minutes between the reviewer’s
decision and the delivery (the relay runs on the platform’s 5-minute tick). 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.
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 | |
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 emits no 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 | |
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 | |
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 | |
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"
}
}
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.4.0: kyc.yaml version 1.2.0-2026-10-27 (SHA-256 c91a695cfde0), mcp.yaml version 0.1.0-2026-10-27 (SHA-256 1a4875efa736), pulse.yaml version 0.9.0-2026-10-27 (SHA-256 95196a58bb5b), v1.yaml version 1.3.0-2026-10-27 (SHA-256 3fd15257a022). Do not edit it by hand.