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)
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)
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:
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.completedis not sent at this point — the session is not finalised yet.verification.approvedorverification.declined— after the operator's final decision.session.completed— sent immediately afterverification.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.declined → session.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)
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)
document.uploaded
A document was uploaded
AML screening (3 events)
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
Verification links (4 events)
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:
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:
Extract
t(timestamp) andv1(signature) from theX-Onekyc-Signatureheader.Build the signed string:
{timestamp}.{raw_body}(the numeric value fromt, a literal dot, then the raw request body).Compute HMAC-SHA256 using your webhook secret (
whsec_...).Compare the result with
v1from the header (use a constant-time comparison).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:
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:
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_reviewpayload (theverificationsarray). 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_enrichmentflag in the flow configuration (defaulttrue).
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:
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:
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):
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.
Verification link events (link.*)
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:
Open the OneKYC admin panel.
Go to Webhooks.
Click "Rotate secret" next to the endpoint.
Copy the new secret — it is shown only once.
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 OKimmediately 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
Re-read this section and the code examples above.
See the API reference (Swagger / OpenAPI) for the detailed specification.
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