For the complete documentation index, see llms.txt. This page is also available as Markdown.

Webhooks

The OneKYC webhook system delivers verification event notifications to your URL in real time. KYC events (sessions, verification, steps, documents, links, etc.) and KYB events (business verification) are both supported. You select the event types when configuring the endpoint in the admin panel.

Key properties:

  • KYC events (sessions, verification, steps, documents, links, etc.) and KYB events (see KYB webhooks)

  • HMAC-SHA256 signing for delivery integrity

  • Automatic retries with exponential backoff (up to 5 attempts)

  • SHA-256 idempotency keys to prevent duplicate processing

  • Delivery log and metrics in the admin panel

You configure webhooks in the admin panel: enter your server URL, select event types (or all types), and save. OneKYC sends signed POST requests for every event.

Configuration

In the Webhooks section of the admin panel, add your server URL, select the event types (or all types), and save. When the endpoint is created, the system generates a secret (with the whsec_ prefix) for signature verification. The secret is shown once — store it in a secure vault.

Event types

KYC events are grouped into the categories below. KYB events (the KYB events category in the admin panel) are documented in KYB webhooks.

Sessions (5 events)

Event
Description

session.created

A new verification session was created

session.started

The session was started (the user began verification)

session.completed

The session is complete — a terminal event, sent only after the final decision (automatic or by a manual review operator)

session.expired

The session expired

session.cancelled

The session was cancelled

Verification (6 events)

Event
Description

verification.approved

Verification approved

verification.declined

Verification declined

verification.needs_review

Verification routed to manual review by an operator (not a terminal state)

verification.ocr.corrected

OCR data was corrected by an operator

verification.resubmit_requested

A reviewer requested step resubmission (Manual Review Resubmit)

verification.resubmit_fulfilled

The applicant resubmitted the requested step

Event order for sessions with manual review

If a session needs manual review, events are sent in the following order:

  1. verification.needs_review — sent right after automated checks finish with a "needs review" decision, or when an operator manually sends a terminal session back to review. session.completed is not sent at this point — the session is not finalised yet.

  2. verification.approved or verification.declined — after the operator's final decision.

  3. session.completed — sent immediately after verification.approved / verification.declined. The two events are sent as one chain.

If you only subscribe to session.completed, you receive that event exactly once — when the session is finalised. You do not see the intermediate "in review" state; subscribe to verification.needs_review for that.

For sessions without manual review (automatic approval/rejection), the event order is: verification.approved / verification.declinedsession.completed.

session.completed is final for the customer-facing webhook cycle. Internally, a reviewer can later send a completed session back to review or request a resubmit; those operator actions emit verification.needs_review or resubmit events and do not replay the original session.completed until a new final decision is made.

Steps (5 events)

Event
Description

step.started

A verification step started

step.completed

A verification step completed

step.failed

The step finished with an error

step.skipped

The step was skipped because an upstream dependency did not pass

step.retried

The step is retried

Documents (1 event)

Event
Description

document.uploaded

A document was uploaded

AML screening (3 events)

Event
Description

aml.screening_completed

AML screening completed

aml.hit_found

A match against an AML watchlist was found

aml.cleared

AML screening completed with no matches

Event
Description

link.created

The verification link was created

link.consumed

The link was consumed

link.expired

The link expired

link.revoked

The link was revoked

Request format and signature

OneKYC sends a POST request with a JSON body to your URL. Every request carries the following headers:

Header
Description

Content-Type

application/json

X-Onekyc-Signature

Signature in the form t=<timestamp>,v1=<signature>

X-Onekyc-Event-Type

Event type (for example, session.created)

X-Onekyc-Event-Id

Event UUID

X-Onekyc-Timestamp

Unix timestamp of the send

User-Agent

OneKYC-Webhook/1.0

HTTP header names are case-insensitive (RFC 7230 §3.2). On the receiving side, use case-insensitive comparison (most HTTP frameworks do this automatically).

Verifying the signature

To verify a webhook's authenticity:

  1. Extract t (timestamp) and v1 (signature) from the X-Onekyc-Signature header.

  2. Build the signed string: {timestamp}.{raw_body} (the numeric value from t, a literal dot, then the raw request body).

  3. Compute HMAC-SHA256 using your webhook secret (whsec_...).

  4. Compare the result with v1 from the header (use a constant-time comparison).

  5. Confirm that the timestamp is within the allowed window (5 minutes is recommended).

Replay protection: reject requests where |now - X-Onekyc-Timestamp| > 300 seconds (5 minutes). Additionally, use the X-Onekyc-Event-Id header (UUID) for deduplication — store processed event IDs and ignore duplicate deliveries.

When the secret is rotated, the header also carries v2=<signature> — the signature with the new key. Both keys are valid during the grace period.

Signature verification examples

Node.js:

Go:

Payload format

Every event uses a single envelope format:

The data field's contents depend on the event type (session, verification, step, document, link, etc.).

Retry policy

If delivery fails (the response is not 2xx), the system performs up to 5 total delivery attempts with exponential backoff (base delay 5 seconds, doubled on each retry, maximum interval 24 hours, with ±20% random jitter to spread the load). After all attempts are exhausted, the event is moved to the dead-letter queue (DLQ).

To acknowledge successful delivery, your server must respond with HTTP 2xx.

Idempotency key (idempotency_key): generated from a SHA-256 hash of the logical event identity and intended for receiver-side deduplication. The value is a 32-character hexadecimal string.

Example: a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Some session decision payloads may include optional enrichment fields when source data is available: applicant.details, applicant.localized, media_files, decision_reasons, and custom_steps. These fields are additive; webhook consumers should ignore fields they do not use.

Event payload examples

Session events (session.*)

Every session event contains a flat data structure with session info. Terminal KYC events such as session.completed also include the enriched applicant and verifications blocks. If the flow contains phone or email OTP steps, the verified values are included as data.applicant.phone and data.applicant.email.

The top-level result field is the source of truth for the final session decision. The verifications array may include historical retry attempts for a check. Each verification item includes attempt_number and is_current when available; use is_current: true as the current per-check result. For example, if document liveness failed on attempt 1 and passed on attempt 2, the terminal approved payload may contain both rows, with only the attempt 2 row marked as current.

The full session field schema is in the API reference (Swagger / OpenAPI).

Verification events (verification.*)

Verification events contain full session info, applicant data extracted from the document, and the results of every check.

verification.approved:

verification.declined:

verification.needs_review:

The full field schema with types, formats, and descriptions is in the API reference (Swagger / OpenAPI).

Step events (step.*)

Step events carry information about the execution of a single verification step:

Extra fields for step.failed:

Field
Description

error_code

Error code

error_message

Error description

max_attempts

Maximum allowed number of attempts (lets you tell whether this was the last attempt)

Extra fields for step.retried:

Field
Description

max_attempts

Maximum allowed number of attempts

Document events (document.*)

document.uploaded:

Intermediate OCR / liveness / face-match / AML-screening / proof-of-address events are NOT delivered as separate webhook events. Their results are included in the verification.approved / verification.declined / verification.needs_review payload (the verifications array). Proof-of-address payloads include the redacted summary only; raw GPS coordinates and storage keys are not sent. See the examples above.

AML screening events (aml.*)

aml.screening_completed:

aml.hit_found:

aml.cleared:

aml.enrichment field (Kontur.Focus)

Status: Generally available since 2026-04-24. Toggled at the AML-screening step level via the enable_focus_enrichment flag in the flow configuration (default true).

After the primary AML screening against Kontur.Compliance lists (sanctions, terrorists, PEP, bankruptcy, etc.), an extra enrichment stage runs through Kontur.Focus API. This stage augments the applicant decision with data from 13 Focus endpoints and produces a merged decision tagged with the source.

Enrichment results are included in the aml.screening_completed payload as an enrichment object:

Enrichment fields:

Field
Type
Description

inn

string

Applicant's INN discovered by the pipeline (source listed in inn_source)

inn_source

string

INN source: ocr / compliance / focus_search / focus_affiliates / manual

inn_confidence

number

Confidence in the discovered INN (0.00–1.00)

compliance_decision

string

Kontur.Compliance decision before merging with Focus: approve / decline / review

compliance_match_count

int

Number of Compliance list matches

merged_decision

string

Final merged decision

merged_matches_count

int

Total number of matches (Compliance + Focus)

merged_risk_score

number

Final risk score (0.000–1.000 in DB, normalised to 0–100 in payload)

hard_block_reason

string | null

Reason for hard block (see below)

enrichment_status

string

pending / running / completed / failed

enrichment_completed_at

string (RFC3339)

When enrichment finished

call_statuses

object

Status of each Focus call: completed / error / skipped / skipped_no_inn

focus_passport field — Focus-side passport check:

Source: the checkPassport Focus API endpoint. Used as a second source for passport verification in addition to the built-in passport_check of the verification step.

focus_sanctions field — hits against Focus sanctions lists:

focus_pep field — PEP register hit:

focus_person_bankruptcy field — individual bankruptcy:

When bankruptcy is found, the item fields include case_number, arbitr_court, stage (observation / financial recovery / external administration / bankruptcy proceedings / asset liquidation), start_date, manager_full_name, etc.

focus_affiliates field — affiliated companies (where the applicant is a director / founder / beneficial owner):

You can restrict the affiliate fields via the affiliate_fields_visibility setting in the AML step configuration — for example, hide ownership share from non-admin roles.

focus_smz_status field — self-employed (NPD) status:

status values: registered (self-employed), not_registered, unknown (could not be checked — for example, no INN or FNS refusal).

focus_fssp_summary field — Federal Bailiff Service records (sole proprietors only):

focus_court_summary field — court cases (sole proprietors only):

Hard-block rules

A non-empty hard_block_reason means Focus data triggered an automatic rejection regardless of the final risk_score. Possible values:

Value
Description

passport_invalid

Focus reported the passport is invalid

bankruptcy_asset_liquidation

The applicant is at the "asset liquidation" stage of bankruptcy

focus_sanctions_hit

A match was found in Focus sanctions lists

affiliate_sanctions_hit

A company affiliated with the applicant is under sanctions

fssp_debt_exceeds_threshold

FSSP debt exceeds the configured hard_block_min_fssp_debt_rub threshold

Each rule can be disabled individually in the AML step configuration (hard_block_passport_invalid, hard_block_bankruptcy_asset_sale, hard_block_focus_sanctions_hit, hard_block_affiliate_sanctions_hit, hard_block_min_fssp_debt_rub set to 0).

Per-endpoint flags

You can toggle each Focus call individually in the AML step configuration (under Flows → AML screening step):

Flag
Focus endpoint
Description

enable_focus_enrichment

Global switch for the entire enrichment stage

enable_sanctioned_persons_check

sanctionedPersons

Search against Focus sanctions lists

enable_pep_search_check

pepSearch

PEP register search

enable_person_bankruptcy_check

personBankruptcy

Individual bankruptcy check

enable_passport_invalid_check

checkPassport

Passport verification with a second source

enable_person_affiliates

personAffiliates

Affiliated companies search

enable_smz_check

smzSend + smzGetStatus

Self-employed status check

enable_fssp_check

fssp

FSSP check (sole proprietors only)

enable_court_cases_check

generalCourtCases

Court cases check (sole proprietors only)

If the global enable_focus_enrichment = false, the enrichment field is absent from the payload and the decision relies on Kontur.Compliance data only.

Every link event carries the full verification-link record:

The full link field schema is in the API reference (Swagger / OpenAPI).

OCR events (verification.ocr.*)

verification.ocr.corrected — an operator edited OCR results in the admin panel:

Manual Review Resubmit events (verification.resubmit_*)

Events for step resubmission requests by manual review operators.

verification.resubmit_requested — a reviewer asked the applicant to resubmit a step:

Allowed reason_code values: illegible, blurry, cropped, glare, wrong_document, expired_document, partial.

verification.resubmit_fulfilled — the applicant resubmitted the requested step:

Manual Review Resubmit limits: up to 3 requests per session, 7-day request TTL. If the applicant does not resubmit before the TTL expires, the request is cancelled automatically.

Frequently asked questions

Lost webhook secret

A webhook secret (with the whsec_ prefix) cannot be recovered — it is shown only once when you create the endpoint.

What to do:

  1. Open the OneKYC admin panel.

  2. Go to Webhooks.

  3. Click "Rotate secret" next to the endpoint.

  4. Copy the new secret — it is shown only once.

  5. Update the secret in your application configuration.

Note: during rotation, both secrets (old and new) are valid at the same time for a 24-hour grace period. This keeps webhook delivery uninterrupted while you update credentials. The X-Onekyc-Signature header carries both v1=<old_signature> and v2=<new_signature>.

No incoming webhooks

If webhooks stop arriving, run through the following checks:

1. Endpoint status in the admin panel

  • Open the Webhooks section of the admin panel.

  • Check the endpoint status (active / disabled / error).

  • Review the delivery log — it lists response codes and error details.

2. Server reachability

  • The URL must be publicly reachable over HTTPS (HTTP is not supported).

  • Make sure your firewall does not block incoming requests from OneKYC.

  • Verify the SSL certificate is valid (self-signed certificates are not supported).

3. Webhook handler code

  • Your endpoint must respond with HTTP 2xx (200, 201, 204) within 30 seconds.

  • If processing takes longer, return 200 OK immediately and process asynchronously.

  • Check your application log for signature validation errors.

4. Event types

  • Confirm that the required event types are enabled in the endpoint settings.

  • If you selected "All events", confirm that events are actually being generated (sessions created, verifications run, etc.).

5. Dead-letter queue

If the system cannot deliver a webhook after 5 attempts (with exponential backoff up to 24 hours), the event is moved to the dead-letter queue. Undelivered events are visible in the admin panel, where you can trigger manual redelivery.

Endpoint testing:

To validate an endpoint, click "Send test webhook" in the admin panel. In the dialog, choose an event type (for example, verification.approved) — the system sends a test payload of that type with a valid HMAC-SHA256 signature. The test event identifier carries the evt_test_ prefix.

Contacting support

If you run into trouble with webhooks, use the following channels:

1. Documentation and examples

2. Technical support

  • Email: info@onekyc.io

  • Telegram: @OneKYC_Support

  • Hours: Mon–Fri, 10:00–19:00 (GMT+6, Almaty)

3. Critical issues

For critical issues blocking production:

  • Send a request to support with "URGENT" in the subject line.

  • Include tenant_id, the endpoint URL, and sample error log entries.

  • A response is provided within 2 hours during business hours.

4. Bug reports and improvement suggestions

If you find a bug in the webhook system or have suggestions:

  • Open a request through the admin panel customer area.

  • Or send the details to info@onekyc.io.


For full details on payload format, headers, and retry policy, see the API reference (Swagger / OpenAPI).

Last updated