Webhook events
Understand webhook event types and payload structure
Webhook payloads are minimal, machine-readable JSON messages that deliver metadata about an event. They will never include personally identifiable information (PII). You can fetch full details from the API when needed by using the respective session_id.
All events use thin payloads. No personally identifiable information (PII) is included by default. To learn more, see the Configure webhooks →
Payload structure
All webhook events include these fields:
| Field | Type | Description |
|---|---|---|
eventId | string | Same ID across retries for the same event |
eventVersion | string | Current event version |
eventName | enum | Format: resource.action. One of session.created, session.completed, session.aborted, session.expired, session.error (see Session events), or step.started, step.progress, step.ended (see Step events). |
eventDeliverId | number | Unique per retry attempt (currently defaulted to a max. 10 times) |
eventSequence | number | Index of the specific event within a session (sequence: 1, 2, 3) |
createdAt | ISO 8601 | When the event was created |
environment | enum | Environment on which endpoint URL was configured. One of live, staging. |
payload.flowId | string | The identifier of the flow that was triggered |
payload.sessionId | UUID | The identifier of the specific session |
payload.sessionStatus | enum | Current status of the session. One of CREATED, RUNNING, COMPLETED, ABORTED, EXPIRED, ERROR. |
payload.lastStep | object | Information about the last executed step. Always present for terminal session events (session.completed, session.aborted, session.expired, session.error). Always absent for session.created. Always present for step events (step.started, step.progress, step.ended). |
payload.lastStep.stepId | string | Unique identifier of the last step. This corresponds to the step name defined in the flow definition. |
payload.lastStep.stepType | string | Type of the last step (e.g. DOC_ID:v2, END, CHOICE) |
payload.lastStep.stepDescription | string | Human-readable description of the step (as configured in the flow). Empty string if not set. |
payload.lastStep.stepStatus | string | Status of the last step. One of STARTED, COMPLETED, ERROR, ABORTED, WORKFLOW_EXPIRED, or ROLLED_BACK. |
payload.lastStep.stepProgress | enum | What a running step is waiting for. One of IN_PROGRESS, ENDUSER_INPUT_NEEDED, IN_MANUAL_REVIEW. Present while stepStatus is STARTED, absent otherwise. See Track step progress. |
payload.lastStep.stepStatusDetails | string | Diagnostic detail when stepStatus is ERROR (optional). See Technical errors for more. |
The eventId remains the same across all retry attempts, while eventDeliverId increments.
JWT-encoded payload
Webhook messages are delivered as a JSON Web Token (JWT) rather than as a plain JSON object. The HTTP request body therefore contains a JWT string, which must be decoded to access the underlying payload.
The JWT must be verified before use, including validation of the signature and standard claims such as iss, aud, sub, exp, and iat. Once successfully decoded and verified, the JWT payload exposes the event metadata and business data under the data field.
aud— equals the destination endpoint URL you configured for the webhook. Validate that this matches your endpoint to prevent cross-endpoint replay attacks.sub— equals theeventIdof the delivered event.
Payload example
The decoded JWT payload has the following structure:
{
"data": {
"eventId": "61a51c5b-480d-4f47-8334-34b0ba7ae0be",
"eventName": "session.completed",
"eventVersion": "1.0",
"eventSequence": 2,
"eventDeliverId": 1,
"createdAt": "2026-01-08T08:35:37.581Z",
"environment": "staging",
"payload": {
"flowId": "bf8e2e27-2340-426f-a0bb-f1081e4a07ac",
"sessionId": "fe1941c2-bc45-4142-b516-fa0fc9fd7754",
"sessionStatus": "COMPLETED",
"lastStep": {
"stepId": "END-ACCEPT",
"stepType": "END",
"stepDescription": "Final step",
"stepStatus": "COMPLETED"
}
}
},
"iss": "https://localhost:3002/oidc",
"iat": 1767861339,
"exp": 1767864939,
"sub": "61a51c5b-480d-4f47-8334-34b0ba7ae0be",
"aud": "https://webhook.site/d076d490-8424-4c85-8904-5d1801c7909e"
}
Session events
IDnow sends the following event types when a session changes state:
| Event Type | Description |
|---|---|
| session.created | The session has been created and is ready to start |
| session.completed | The session reached an End step with a final outcome |
| session.expired | The session expired before it finished and can no longer be used |
| session.aborted | The user left or cancelled the session before it reached a final outcome |
| session.error | The session stopped because of an error |
session.completed means the flow finished, not that the user passed. For what to do with each event, see Interpret session results.
Step events
Session events tell you how a session starts and ends. Step events tell you what happens in between. You select them next to the session events when you configure a webhook, and they follow the same delivery, retry and security rules, including the JWT-encoded payload described above.
IDnow sends the following event types while a step runs:
| Event Type | Description |
|---|---|
| step.started | A step begins. Its progress is IN_PROGRESS |
| step.progress | The progress of a running step changes, for example to IN_MANUAL_REVIEW when it enters a manual review |
| step.ended | A step finishes. The flow either moved on to the next step, or sent the user back (rolled back) |
step.ended means the step finished, not that the user passed. Only the session's outcome holds that decision. A step that is aborted or fails has no step event: use session.aborted and session.error.
step.progress comes with a progress value that says what the running step is waiting for. IN_MANUAL_REVIEW is one example: listen for it to know when a user moves to a manual review. For all progress values, what each one means and a worked example, see Track step progress.