Skip to main content
Session webhooks notify your application in real-time as users progress through KYC onboarding. Receive instant updates when sessions are created, completed, processed, or when their status changes.

Event Types

kyc.session.created

New session created via your backend

kyc.session.completed

User finished all workflow steps

kyc.session.processed

Processing complete with decision

kyc.session.approved

Session manually approved

kyc.session.rejected

Session manually rejected

kyc.session.manual_review

Sent to manual review queue

kyc.session.correction_requested

Reviewer requested customer remediation

kyc.session.correction_resolved

Customer resolved a remediation request

Session Lifecycle

Flagged vs Manual Review: A flagged session is automatically approved but marked for attention. The session status is APPROVED, but final_decision is flagged. Use this to approve users while tracking risk signals for monitoring.

Configuration

Enable Session Webhooks

Configure your webhook endpoint in your tenant’s notification settings:

Response

A webhook_secret is automatically generated when you set a webhook_url. Use this secret to verify webhook signatures. The full secret is only shown once - store it securely.

Regenerate Webhook Secret

To generate a new secret (invalidating the old one):
Then set the webhook_url again to generate a new secret.

Webhook Delivery

Request Format

Webhooks are delivered as POST requests with the following structure: Headers:
Body (Envelope):

Headers Reference


Signature Verification

All webhooks are signed using HMAC-SHA256. Verify the signature to ensure the webhook is authentic and hasn’t been tampered with.

Signature Algorithm

Where:
  • timestamp = Value from X-Webhook-Timestamp header
  • payload = Raw JSON body (the entire envelope)
  • webhook_secret = Your tenant’s webhook secret

Implementation Examples

Replay Attack Prevention

To prevent replay attacks, verify that the timestamp is recent:

Event Payloads

kyc.session.created

Sent when a new session is created via POST /kyc/sessions.
Data Fields:

kyc.session.completed

Sent when the end-user completes all workflow steps. The session is now ready for processing.
This event means the user has finished their part. Processing (document extraction, validations, automation rules) happens next - either automatically if auto_process is enabled, or when you call POST /kyc/sessions/{id}/process.
Data Fields:

Form Data Object

The form_data object contains all form fields completed by the user, organized by a portable key. This makes it easy to access specific fields programmatically without relying on auto-generated field IDs. Structure:
Portable Field Keys: Define a field_key (e.g., full_name, date_of_birth) in your workflow to get consistent keys across environments. This eliminates the need to map auto-generated field_id values between staging and production.

kyc.session.processed

Sent after session processing completes. This is the most important webhook for automation - it contains the final decision and all automation rule evaluations. The final_decision can be one of:
  • approved - Session automatically approved, no issues found
  • flagged - Session approved but marked for attention (e.g., high-risk country, unusual patterns)
  • rejected - Session automatically denied (e.g., sanctions match, fraud detected)
  • manual_review - Requires human review before final decision
Data Fields:
Understanding status vs final_decision:
  • status is the session state in the system (approved, rejected, manual_review)
  • final_decision is what the automation decided (approved, flagged, rejected, manual_review)
A flagged decision results in status: approved because the session is approved, but the final_decision: flagged tells you it needs monitoring.

Web validation in webhooks

When a session runs web validation (from an auto-validated URL form field or from subject_data.website / web_url / url / domain declared at creation), the kyc.session.processed webhook (and status events that carry automation_result, such as approved, rejected, manual_review) includes: The webhook does not include the full web_validation_results array (reliability score breakdown, SSL/whois/classifier objects, etc.). Fetch that from the Sessions API:
data.processing_results.web_validation_results See Sessions — Subject data and Web validation results.
Pre-declaring a URL in subject_data does not add a separate webhook event at creation time. The URL is validated during session processing; webhook consumers should listen for kyc.session.processed.

Web validation flags

automation_result.flags only contains flags for rules that triggered — i.e. conditions the website failed or that require attention. A clean website produces no web flags, with one exception: high_website_score is a positive flag emitted when a score rule triggers and the reliability score is ≥ 80. Every flag comes from a triggered rule, so each entry in flags has a corresponding entry in triggered_rules whose rule_type is listed below. To identify what was rejected, filter triggered_rules by action (auto_deny, manual_review) rather than relying on flags alone — some triggered rules (e.g. custom score thresholds, missing social platforms) do not emit a flag.
Separately from automation_result.flags, the risk_scoring block has its own critical_flags array which may include web_low_reliability (reliability score < 30 or validation error) and web_adverse_media (HIGH_RISK adverse media). These are risk-scoring flags, not automation rule flags.

Automation Result Object

Triggered Rule Object

Rule Types

Flags by rule type (non-web)

Like the web validation flags, these flags only appear when the corresponding rule triggers. Some triggered rules do not emit a flag, so always use triggered_rules (filtered by action) as the source of truth for what caused a rejection.

Action Priority

Actions are evaluated in priority order. The highest-priority triggered action becomes the final_action:

KYB: UBO subsessions and extracted facts

For business (KYB) verifications, a company session can have subsessions — separate verification flows for each Ultimate Beneficial Owner (UBO), director, or legal representative. Instead of correlating multiple individual webhooks, the platform embeds the relevant subsession data inside the parent company’s webhook, so you receive the full structure in a single delivery. Two related blocks are included:
  • subsessions — an array with one entry per linked subsession (e.g. each UBO), carrying its status, decision, classification (tag/label/roles), submitted form_data, and a curated extracted block.
  • extracted (top level) — curated facts pulled from the parent session’s own documents. This is used when the subject’s data lives on the main session rather than a subsession (e.g. an individual or sole-trader flow with no UBO subsessions).
These blocks are included in the kyc.session.completed, kyc.session.processed, kyc.session.approved, kyc.session.rejected, kyc.session.manual_review, kyc.session.correction_requested, and kyc.session.correction_resolved events. They are null when not applicable (e.g. an individual session has no subsessions; a session whose documents yielded no facts has no extracted).

subsessions[] object

extracted object

The extracted block (both at the top level and inside each subsession) is a flat object with the curated facts found in that session’s documents. Keys are present only when a value was found:

How residence country is resolved

The residence country is derived from the strongest available evidence, in priority order:
  1. A dedicated proof-of-address document — its country is used as the country of residence.
  2. An identity document that also carries a printed address (e.g. the Argentine DNI, whose reverse shows the holder’s domicile and is accepted in lieu of a separate proof of address) — the document’s issuing country is taken as the country of residence.
If neither a proof of address nor an identity document with an address is present, residence_country is omitted.
To populate residence_country from an Argentine DNI, the back/reverse of the document (the side with the address) must be captured. If only the front is provided, the address — and therefore the residence country — cannot be extracted.

Example: KYB processed webhook with UBO subsessions

In this example the first UBO’s residence_country (AR) was derived from the address on the Argentine DNI, while the second UBO’s (UY) came from a dedicated proof-of-address document. Each UBO’s date_of_birth and any declared expected_monthly_transactions are available directly in the parent webhook.

Triggered Rules Reference

Each triggered_rule object in the automation_result.triggered_rules array contains a message field with a human-readable explanation. Use this to communicate rejection reasons to your users.
The message field is designed to be user-friendly and can be displayed directly to your customers. The rule_details object contains machine-readable data for programmatic handling.

Sanctions & Watchlist Match (list_match)

Triggered when a name matches sanctions or watchlist databases.

Country Restriction (country)

Triggered when nationality, residence, or incorporation country matches a blocked list.

Face Match Failed (face_match)

Triggered when the selfie doesn’t match the ID photo.

Face Deduplication (face_dedup)

Triggered when the same face is detected in previous sessions.

Document Expired or Near Expiry (document)

Triggered when documents are expired or about to expire. Expired document:
Document too old (e.g., proof of address):

Invalid Document Type (document_validity)

Triggered when uploaded document doesn’t match expected type.

Email Verification (email)

Triggered when email validation detects issues.
Possible check_type values:

Transaction Volume (volume)

Triggered when declared transaction volume exceeds thresholds.

Time to Complete (time_to_complete)

Triggered when session completion time is suspicious. Too fast (possible bot):

Form Field Declaration (form_field)

Triggered when user declares high-risk information in forms.

Web Validation Rules

These rules appear in automation_result.triggered_rules on kyc.session.processed and related status webhooks. For the complete validator output (scores, SSL, whois, classifier, policies), use GET /kyc/sessions/{session_id}processing_results.web_validation_results. Multiple rule types for website validation:

SSL Certificate (web_ssl)

Blocked Industry (web_industry)

Adverse Media (web_adverse_media)

Domain Sanctioned (web_sanction)


Extraction Validation (extraction_validation)

Triggered when extracted document data fails validation. Sanctions match on extracted name:
Form data mismatch:

Using Triggered Rules in Your Application

Displaying Rejection Reasons

Logging for Compliance


kyc.session.approved

Sent when a compliance officer manually approves a session. Includes the full session context: automation evaluation from processing, form data, and workflow information.
Data Fields:

kyc.session.rejected

Sent when a compliance officer manually rejects a session. Includes the full session context: automation evaluation from processing, form data, and workflow information.
Data Fields:

kyc.session.manual_review

Sent when a session is flagged for manual review (either automatically or manually). Includes the full session context: automation evaluation from processing, form data, and workflow information.
Data Fields:

kyc.session.correction_requested

Sent when a reviewer requests customer remediation with POST /kyc/sessions/{session_id}/corrections. The session status becomes awaiting_client_correction and the affected steps become needs_correction.
Data Fields:

kyc.session.correction_resolved

Sent when the customer completes a step that had an open correction request. The resulting status depends on whether corrections remain:
  • More corrections pendingstatus stays awaiting_client_correction and next_step_id points to the next step that still needs correction.
  • All corrections resolvedstatus follows the workflow’s validation_config.correction_resolution.mode:
Both the completed and auto_process modes emit this webhook with status: "completed". The status field never contains the literal value auto_process — that is a workflow configuration mode, not a session status. The difference is behavioral: auto_process automatically kicks off processing (and a kyc.session.processed webhook), while completed waits for you to call POST /process.
status stays awaiting_client_correction because another step still has an open correction. Use next_step_id to point the customer to it.
Data Fields:

Handling Webhooks

Example: Full Webhook Handler


Testing Webhooks

Using the Staging Environment

Always test webhooks in staging first:
  1. Configure your staging webhook URL
  2. Create test sessions
  3. Complete the flow to trigger events
  4. Verify your handler processes them correctly

Local Development with ngrok

Webhook Payload Generator

For testing your signature verification, generate test payloads:

Error Handling

Your Endpoint Should:

  1. Return 2xx quickly - Within 10 seconds
  2. Process asynchronously - Use background tasks
  3. Be idempotent - Handle duplicate deliveries
  4. Log failures - For debugging

Common Issues


Rate Limits

Webhooks are not subject to API rate limits, but your endpoint should handle bursts:
  • Peak: Up to 100 webhooks/minute during high activity
  • Timeout: 10 seconds per webhook delivery
  • No retries: Failed webhooks are not automatically retried
For high-volume scenarios, consider using a queue-based architecture (SQS, Redis, RabbitMQ) to buffer incoming webhooks.

See Also

Sessions API

Create and manage KYC sessions

Workflows API

Configure automation rules and steps

Tenant Configuration

Manage notification channels

Webhooks Overview

General webhook concepts and security