FLOW & CHECK LIST
How a verification session runs, end to end
This page walks through every step from the moment an end user enters the flow to the moment a signed webhook reaches your system — the checks that run at each step, and the exact numbers behind each decision.
Every weight, threshold and limit below is a value actually enforced in the platform. Per-tenant configuration can only tighten them, never loosen them.
- 8
- Flow steps — from session creation to webhook delivery
- 19
- Distinct document checks — 15 scored + 4 hard rules
- 5
- Document profiles — each with its own weight set
- 3
- Liveness layers — active, passive ML, heuristic
Step by step
Session flow
- 01Your system
Session creation
Your backend opens a verification session. The tenant API key is used only in this call and never reaches the browser. The returned session_id is the only user identifier exposed externally.
POST /v1/sessions · X-Api-Key · state: created
Checks that run at this step
- Velocity limit: at most 5 sessions per applicant per 24 hours
- Velocity limit: at most 20 sessions per IP per 24 hours
- Tenant balance check on production sessions
- Reusable KYC: an approved verification newer than 180 days with no AML alert skips re-verification
- 02End user
Opening the flow
The user opens the WebSDK (iframe). Instead of an API key, the SDK works with a short-lived JWT scoped to that session, valid for 15 minutes by default. Tenant theme and the user's language (54 languages) are applied at this step.
GET /v1/sessions/{id}/config · short-lived JWT (15 min) · CORS allowlist
Checks that run at this step
- Session validity window: 60 minutes (it moves to expired past that)
- Per-tenant CORS allowlist — the SDK only loads from declared origins
- Active modules (document / liveness) are read from the session config
- 03End user
Document selection and capture
The user picks a document type and captures it with the camera. Card edges are detected and cropped automatically; overly blurry or dark frames are flagged on-device and re-captured before ever reaching the server.
POST /v1/sessions/{id}/access-keys · POST /{id}/documents/images
Checks that run at this step
- Supported types: Turkish ID card, passport, driving licence, residence permit, Ghana Card
- The back side is mandatory where the profile requires it (TD1 MRZ sits on the back)
- On-device quality gates: sharpness, brightness, framing
- 04IDotta platform
Document analysis
The session moves to document_processing and a background worker analyzes the images: field reading with our in-house OCR model, MRZ decoding via a separate path calibrated for OCR-B, ICAO 9303 check digit validation, and tamper detection via ELA and texture analysis. Each check produces a 0–100 score, reduced to a single document score by the profile weights.
state: document_processing → liveness_pending | review | rejected
Checks that run at this step
- 15 scored checks (with per-profile weights in the table below)
- 4 unweighted hard rules: document presence, file integrity, document expiry, specimen detection
- Cross-field consistency between front and back (MRZ ↔ printed fields)
- 05IDotta platform
AML / sanctions screening
The name extracted from the document is screened against open sanctions lists synced daily. Names are normalized (accents and Turkish characters folded), records with a fuzzy match score of 90% or above become candidates, and candidates whose birth year conflicts are eliminated.
rapidfuzz token_sort_ratio ≥ 90% · match → state: review
Checks that run at this step
- OFAC SDN (U.S. Treasury), UN Consolidated, EU FSF and UK Sanctions List (FCDO) lists
- False-positive elimination via birth-year conflict checks
- A match never auto-rejects — the decision is left to a human
- 06End user
Liveness detection and face matching
The server generates a random sequence of 3–4 challenges (at least one blink and one head movement are guaranteed) alongside a single-use identifier. The user submits one frame per challenge plus a selfie; the server verifies each frame independently against its challenge type, then biometrically compares the selfie to the document photo.
POST /{id}/liveness/start → /{id}/liveness/submit · challenge TTL 300 s
Checks that run at this step
- Every active challenge must pass — no tolerance
- Frame-to-frame consistency: same person, and no static replay
- Passive anti-spoofing (in-house model) + heuristic LBP/FFT/YCrCb signals
- Document photo vs selfie comparison with our in-house face model
- Each approved face is written to pgvector; the same face under a different identity raises a duplicate signal
- 07IDotta platform
Decision and delivery
The outcome of every layer is reduced to a single terminal state. Whatever the decision, each state transition is written to a timestamped event log. The result is delivered to your system via an HMAC-SHA256 signed webhook, and can always be read back from the result endpoint.
webhook · X-Signature: sha256=… · GET /v1/sessions/{id}
Checks that run at this step
- Final statuses: approved, declined, expired, abandoned; in_review awaits an analyst and does not expire
- A rejected step keeps the session in_progress and grants the user 3 retry attempts; once they run out it is declined
- Failed webhook deliveries are retried; delivery records are retained
- Personal data is deleted automatically 90 days after the terminal state
END-USER FLOW
The six screens your user sees
The flow is white-label — shown here in the IDotta theme. Tenants supply their own colours and logo, and the steps shrink to the modules you selected.
- 9:411 / 5
Verify your identity
Takes about 2 minutes. You'll need your ID and a well-lit room.
StartSecured by
Intro & consent
- 9:412 / 5
Scan the front of your ID
Place the document in the frame; the card is detected and cropped automatically.
align documentCapturing…Secured by
Document capture
- 9:413 / 5
Look at the camera
Follow the on-screen prompts — you may be asked to blink or turn your head.
faceChecking liveness…Secured by
Liveness & selfie
- 9:414 / 5
Almost done
The checks are running on the server. Don't close this screen.
- Document✓
- Liveness✓
- Face match·
Please waitSecured by
Processing
- 9:415 / 5
You're verified
The result was delivered to your system over a signed webhook.
ContinueSecured by
Result
State machine
Verification statuses
Every response and webhook you receive carries status (the key your code checks), status_label (the display name) and step (the internal step, informational only). Internal steps only make defined transitions, each one written to the audit log, so replay attempts that resubmit an earlier step are structurally blocked.
not_started
Not Started
Session opened; the user has not opened the link yet.
in_progress
In Progress
The user is going through the steps. A rejected step stays here while retries remain.
resubmitted
Resubmitted
An analyst asked for some steps to be redone; the user has not finished yet.
in_review
In Review
No automatic decision; awaiting an analyst. Does not expire.
approved
Approved
All checks passed — identity verified.
kyc_expired
Kyc Expired
The approval is older than 365 days; request re-verification.
declined
Declined
Final decline: an analyst declined, retries ran out, or time ran out with retries left.
expired
Expired
60 minutes passed before the user started.
abandoned
Abandoned
The user started but did not finish within 60 minutes.
Document engine
Check list and weights
Each document type carries its own weight set — the MRZ dominates on a passport, while text readability leads on a driving licence that has no MRZ. The percentages below are the contribution shares when every check runs.
| Check | TR ID Card | Passport | Driving Lic. | Residence P. | Ghana Card |
|---|---|---|---|---|---|
| MRZ ValidationICAO 9303 machine-readable zone: check digits and field consistency. | 19.5% | 27.1% | — | 25.3% | 23.5% |
| Text ReadabilityHow many document-specific keyword groups OCR can find. | 8.8% | 10.4% | 19.8% | 12.6% | 9.8% |
| Sharpness / FocusBlur measured via Laplacian variance. | 8.8% | 10.4% | 12.3% | 10.5% | 9.8% |
| Tampering (ELA)Error Level Analysis: traces of re-saved or edited regions. | 7.1% | 8.3% | 9.9% | 8.4% | 7.8% |
| Face DetectionPresence and placement of the biometric portrait on the document. | 7.1% | 8.3% | 9.9% | 8.4% | 7.8% |
| Screen/Print DetectionMoiré patterns via FFT periodic peak analysis — a photo of a screen. | 7.1% | 8.3% | 9.9% | 8.4% | 7.8% |
| Document ContourID-1 rectangular edge detection and the 1.586:1 aspect ratio. | 5.3% | 6.3% | 7.4% | 6.3% | 5.9% |
| Brightness & GlareOverly dark, blown-out or flash-glared frames. | 4.4% | 5.2% | 6.2% | 5.3% | 4.9% |
| Local Texture ConsistencyRegional texture deviation — region-level splicing detection. | 4.4% | 5.2% | 6.2% | 5.3% | 4.9% |
| ResolutionA readability floor of at least 600×400 pixels. | 2.7% | 3.1% | 3.7% | 3.2% | 2.9% |
| TR National ID AlgorithmMathematical checksum validation of the Turkish national ID number. | 8.0% | — | 14.8% | — | — |
| Ghana Card PINGHA-XXXXXXXXX-X format plus cross-check against the MRZ. | — | — | — | — | 8.8% |
| Colour SignatureThe turquoise/pink print colour signature of the Turkish ID card. | 7.1% | — | — | — | — |
| Micro PrintPresence of the repeating fine print pattern on the back side. | 4.4% | — | — | — | — |
| Data ConsistencyCross-comparison of MRZ fields against the printed fields on the front. | 5.3% | 7.3% | — | 6.3% | 5.9% |
A check that could not run (the MRZ with no back side, for example) never enters the score; the remaining checks are rescaled among themselves. Missing data therefore does not behave like a low score.
Unweighted hard rules
These four checks do not enter the score — they set the outcome directly. Even at a score of 100, a failure here blocks automatic approval.
- DOCUMENT_PRESENCE
Document Presence
Each side must show a document: a keyword group, a verified MRZ or at least 60 readable characters — checked before any scoring.
- INTEGRITY
File Integrity
Corrupt, empty or under-5 KB files — no other check is run.
- EXPIRY
Document Validity
A check-digit-verified expiry date in the past is a hard reject.
- SPECIMEN
Specimen Detection
Specimen markings and known sample document numbers.
Scoring
How the decision is made
Every check produces a score between 0 and 100. The document score is the weighted average of the checks that ran, and falls into one of three decision bands.
Document score
Σ (check score × weight) ÷ Σ (weight)
Thresholds can be raised per tenant but never lowered — the rule engine clamps every value to the global floor.
- ≥ 70Automatic passDocument passed; the flow advances to liveness (or straight to approval in document-only mode).
- 45 – 69Manual reviewA document was detected but could not be fully verified; an operator decides.
- < 45Automatic rejectThe checks do not support the validity of the document.
Hard rules that override the score
In these cases the decision is as follows regardless of the weighted score.
Face but no text → reject
Catches users who upload a selfie instead of a document. The rule is disabled once the MRZ is verified, so a genuine card is never rejected by mistake.
No document on a side → reject
Checked side by side, before scoring: a selfie or blank photo on either side, or the same photo uploaded twice, is rejected and the reason is shown to the user. The MRZ on the back cannot vouch for the front.
Expired document → reject
A hard reject when the expiry date is verified by its MRZ check digit and lies in the past.
Specimen document → reject
A conclusive specimen marking rejects; a weak or ambiguous marking goes to manual review instead of automatic approval.
National ID cross-check failed → review
Splicing the two sides of different cards is only visible in the cross-comparison; automatic approval is blocked and a human decides.
AML match → review
A sanctions list match never produces an automatic rejection under any circumstances.
Duplicate face → review
If the same face was previously approved under a different identity, the session is routed to a human.
Required back side missing → reject
ID cards and residence permits carry the MRZ on the back; without the back image the document cannot be verified, whatever the front scores.
MRZ not verified → review
On a document with an MRZ, automatic approval requires a verified MRZ (at most one failed check digit); an unreadable or invalid MRZ sends the session to a human.
Any check failed → review
If any scored check fails (for example a screen capture, image tampering or an MRZ/printed data mismatch), no automatic approval is given, however well the others score.
AML screening not performed → review
If no name can be extracted, the lists are not loaded or screening errors, the result is not treated as 'no match'; the session goes to a human.
Face comparison not performed → review
If the selfie cannot be compared with the portrait on the document or the chip, or the duplicate face search cannot run, no automatic approval is given.
Liveness
Three layers of liveness assurance
The layers complement each other: the active layer proves the user is really there, the passive layer proves what faces the camera is not a screen or a print, and the heuristic layer looks for artifacts the other two miss.
- 01
Active challenges
The server generates a random sequence (blink, head turn). Each frame is verified independently on the server against its challenge type — the client claiming 'passed' counts for nothing.
In-house face detectorEye stateHead pose
- 02
Passive anti-spoofing
Our in-house anti-spoofing model, trained to catch print, screen replay and moiré artifacts, produces a single genuineness probability.
In-house modelPrint / screen replayServer-side
- 03
Heuristic signals
Texture, frequency and colour-space heuristics (LBP / FFT / YCrCb), frame-to-frame consistency and optical-flow motion analysis are weighed as secondary signals.
LBPFFTOptical flow
Liveness confidence score
(passive score × 0.35 + face match × 0.65) × 100
Two thirds of the weight sits on the face match: a genuine face that does not belong to the person on the document is not rescued by a high passive score. And the score alone is not enough — every gate beside it must be cleared as well.
Gates that must be cleared
- Active challenges
- allA single missing or failed frame fails the attempt; the user can retry.
- Passive score floor
- ≥ 0.40Lower bound on the ensemble genuineness probability.
- Anti-spoof gate
- ≥ 0.55Ensemble threshold for a 'genuine face' verdict.
- Confidence score
- ≥ 60Lower bound on the combined liveness confidence (a tenant may raise it).
- Challenge TTL
- 300 sValidity window of the single-use challenge id — it closes the replay window.
Face matching and duplicate detection
The document photo and the selfie are compared by our in-house face model. Every approved face is written to a vector database, which catches the same face enrolling again under a different identity.
0.68
Match threshold
Lower bound on cosine similarity between document photo and selfie.
0.60
Quality gate
Face detection score — no embedding is produced from blurry or partial faces.
0.75
Duplicate threshold
Normalized cosine; above it is a 'same person' suspicion and goes to manual review.
AML
Sanctions list screening
Screening uses publicly available lists only; identity data is never sent to a third-party commercial data vendor. Lists are downloaded by a daily cron and written to a local database.
OFAC SDN
U.S. Department of the Treasury — Specially Designated Nationals list (CSV).
UN Consolidated
United Nations consolidated sanctions list (XML).
EU FSF
European Union Financial Sanctions Files (CSV).
UK Sanctions List
UK Foreign, Commonwealth & Development Office (FCDO) — the single UK sanctions list (CSV).
PEP (optional)
An operator-supplied politically exposed persons source — skipped when not configured.
How matching works
- 01The name is normalized: accents and Turkish characters are folded, leaving only A–Z and single spaces.
- 02Fuzzy matching runs via rapidfuzz token_sort_ratio, so a different word order does not break the match.
- 03Records scoring 90% or above are taken as candidates.
- 04A candidate whose birth year conflicts with the one read from the document is eliminated (false-positive filter).
- 05If any match survives, the session goes to manual review and the match details are written to the record.
A match never produces an automatic rejection. Because sanctions lists can contain people who merely share a name, the final decision always rests with a human.
Abuse protection
Limits and windows
All of these are counted per environment — sandbox testing never consumes a production limit.
- 5 / 24 h
- Sessions / applicant
- Daily sessions openable for the same applicant_external_id.
- 20 / 24 h
- Sessions / IP
- Daily sessions an end user can start from the same IP address.
- 3
- Retries
- Retry attempts granted after a rejection; beyond that the API returns 409.
- 60 min
- Session lifetime
- An incomplete session becomes expired after this window.
- 15 min
- SDK token
- Lifetime of the short-lived JWT used by the WebSDK.
- 180 days
- KYC reuse
- An approved verification newer than this is not asked for again (absent an AML alert).
- 90 days
- PII retention
- How long after the terminal state personal data is automatically deleted.
Delivery
Getting the result into your system
A distinct event is produced for every status change. Webhooks are signed with HMAC-SHA256; undeliverable ones are retried, and every delivery attempt is recorded.
- document.verifiedThe document (or chip) was verified; the flow moved on to liveness.
- document.review_requiredThe document could not be auto-verified, or there is an AML match — an operator decides.
- document.rejectedDocument checks failed; the status in the body says whether it is retryable (in_progress) or final (declined).
- liveness.failedOne of the liveness layers was not cleared.
- session.manual_reviewLiveness passed but there is a duplicate-face suspicion.
- session.approvedAll checks passed — identity verified.
- session.rejectedFinal decline (status: declined).
- session.resubmittedAn analyst asked for steps to be redone; send the user a new link.
- session.expiredTime ran out: the user never started (expired) or did not finish (abandoned).
- session.kyc_expiredAn approved verification reached 365 days.
# webhook
X-Signature: sha256=9d41…
{ "event_type": "session.approved",
"session_id": "s_7f3a…",
"status": "approved",
"status_label": "Approved",
"environment": "production" }
Beyond the webhook, the result can always be read via GET /v1/sessions/{id}, so a missed webhook never stalls the flow.
Important note
This page describes the platform's current behavior. Thresholds and limits may be configured more strictly in your tenant agreement; loosening them is not possible.
Ready to integrate?
Get in touch for a sandbox environment, API keys and integration documentation.