An engineer's reference for shipping KYC verification: the three components a regulator expects, then the working code that implements them. Runnable examples live in examples/, the data and jurisdiction requirements live in REQUIREMENTS.md, and every API call here comes from iDenfy's public documentation rather than from a guess.
Two audiences usually own the two halves of this problem and rarely read each other's notes. Compliance owns what has to happen. Engineering owns what actually happens in the code path. The gap between them is where onboarding flows go wrong, so this repo puts both in one place and maps each obligation to the request that satisfies it.
Know Your Customer verification means identifying a customer, assessing the risk they carry, and continuing to check both for as long as the relationship lasts. In the US, FinCEN sets the requirements under the Bank Secrecy Act, and they resolve into three components. All three are obligations of the business, not of the vendor supplying the checks.
1. Customer Identification Program. Collect and verify identity at onboarding, keep records, and screen the verified identity against government lists. Section 326 of the USA PATRIOT Act sets the shape: identify the person opening the account, verify them, retain the evidence, and check them against sanctions, politically exposed person lists, adverse media and watchlists. Field-level requirements are in REQUIREMENTS.md.
2. Transaction monitoring. Watch deposits, transfers and withdrawals for patterns that suggest laundering or terrorist financing, flag them by rule, and investigate the alerts. FATF guidance is that monitoring runs continuously or on defined triggers, scoped to your own institutional risk assessment rather than to a template.
3. Risk management. Identify, assess and mitigate the financial-crime risk the business carries, per customer and across the book. This is the component teams underinvest in, because it produces no user-facing screen. It is also the one that decides how the other two are configured.
A single document check satisfies none of the three on its own. It is one input into component one.
signup ──> create session ──> user completes ──> webhook ──> your decision
│ │ │ │
POST /api/v2/token hosted UI status + approve, deny,
returns authToken, or SDK final flag or route to review
scanRef, redirectUrl │
log against scanRef
Three details in that diagram cause most integration bugs.
scanRef is the identifier you store, not authToken. The token is a short-lived credential for launching the flow; scanRef is the permanent handle you attach to your audit record and use for every later lookup.
The first webhook is not always the last one. IDENTIFICATION_AUTO_FINISHED can arrive with final: false, which means a reviewer is still working the case and an IDENTIFICATION_MANUAL_FINISHED event is coming. Acting on a non-final result is the classic version of this bug: you onboard or reject somebody twice.
A session costs money. Do not create a second session for the same clientId before the first one resolves.
Basic auth with your API key and secret, one required field.
export IDENFY_API_KEY=...
export IDENFY_API_SECRET=...
bash examples/create_session.sh minimalcurl -X POST https://ivs.idenfy.com/api/v2/token \
-u "$IDENFY_API_KEY:$IDENFY_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"clientId": "user-123"}'{
"authToken": "pgYQX0z2T8msB64gkl...",
"scanRef": "ec6a7108-8c26-11e9-9758-309c231b1bac",
"clientId": "user-123",
"redirectUrl": "https://ivs.idenfy.com/api/v2/redirect?authToken=pgYQX0z2T8...",
"expiryTime": 3600,
"sessionLength": 600
}Send the user to redirectUrl, or embed the flow with authToken, or hand the token to the mobile SDK. Same session either way.
The interesting version is the configured one, because the session payload is where compliance policy becomes code. Passing the identity you already hold turns the check into a comparison: a mismatch on name or date of birth comes back as a flag rather than a silent pass.
{
"clientId": "user-123",
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "1990-05-15",
"country": ["US", "GB", "DE"],
"documents": ["PASSPORT", "ID_CARD", "DRIVER_LICENSE"],
"ageLimit": 18,
"checkLiveness": true,
"checkAml": true,
"checkDuplicateFaces": true,
"checkIpProxy": true,
"callbackUrl": "https://yourapp.com/webhooks/idenfy",
"expiryTime": 3600,
"sessionLength": 600
}| Parameter | Which component it serves |
|---|---|
firstName, lastName, dateOfBirth |
CIP data matching. Mismatches surface as mismatchTags instead of passing quietly |
country, documents |
Your accept list. Sets the expected document country and the types you take |
ageLimit, ageMax |
Age-restricted sectors. Below or above the bound produces SUSPECTED rather than a hard fail |
checkLiveness |
Active 3D liveness, for the class of attack a photo defeats |
checkAml |
Adds the verified identity to ongoing AML monitoring, which is component one's screening leg plus part of component three |
checkDuplicateFaces, checkDuplicateDocFaces, checkDuplicatePersonalData |
Multi-accounting and fraud rings, which no document check sees |
checkIpProxy |
A risk signal for component three, not a verification result |
riskAssessmentProfile |
Applies a configured risk profile to the session |
reviewFailed, reviewSuccessful |
Forces human review on outcomes you do not want decided automatically |
Full parameter reference: iDenfy session creation docs. Most of these can be set as dashboard defaults, so pass them per session only where the policy varies by user.
Working versions in three languages, all dependency-free:
bash examples/create_session.sh full # curl
python3 examples/create_session.py --full # urllib, stdlib only
node examples/create_session.js --full # fetch, Node 18+Configure a webhook endpoint, or override it per session with callbackUrl. iDenfy POSTs JSON, expects a 2xx inside 10 seconds, and retries when it does not get one.
# examples/webhook_receiver.py, trimmed
signature = headers.get("Idenfy-Signature", "")
expected = hmac.new(SIGNING_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return 401 # never parse an unverified body
event = json.loads(raw_body)
if not event.get("final"):
return 200 # a manual review result is still coming
decide(event["scanRef"], event["status"]["overall"], event.get("fraudTags", []))Verify the signature over the raw bytes, before parsing. Compare in constant time. Return 200 and do the work asynchronously, because a slow handler turns into duplicate deliveries.
The events, and what each one means for your state machine:
| Event | final |
What to do |
|---|---|---|
IDENTIFICATION_AUTO_FINISHED |
true |
Automated decision, conclusive. Act on it |
IDENTIFICATION_AUTO_FINISHED |
false |
Preliminary. Hold, a manual result is coming |
IDENTIFICATION_MANUAL_FINISHED |
true |
A reviewer decided. Always final |
IDENTIFICATION_RESUBMITTED |
true or false |
User re-submitted requested material. Check the flag before acting |
IDENTIFICATION_EXPIRED |
n/a | Token ran out. New session if they still need to verify |
IDENTIFICATION_CANCELLED |
n/a | Abandoned or cancelled. Same |
Webhooks are the primary channel and polling is the fallback, not the design. When a delivery is lost, look the session up rather than waiting:
bash examples/lookup_status.sh ec6a7108-8c26-11e9-9758-309c231b1bacPOST /api/v2/status returns the overall status plus autoDocument, autoFace, manualDocument, manualFace, fraudTags and mismatchTags. POST /api/v2/data returns the extracted document fields.
Five statuses matter: APPROVED, DENIED, SUSPECTED, REVIEWING, EXPIRED. Four of them map to obvious code paths. SUSPECTED does not, and it is the one that will sit unhandled in production.
SUSPECTED means the checks may well have passed and something turned up worth a second look. Read manualDocument and manualFace first: if they come back DOC_VALIDATED and FACE_MATCH, the document and the person are fine and only the tags are in question. Then read the tags themselves. fraudTags carries indicators such as a watchlist hit or a duplicate face. mismatchTags carries disagreements between what you passed at session creation and what the document said.
iDenfy deliberately does not resolve SUSPECTED for you. Whether a name mismatch, an age flag or a sanctions hit disqualifies a customer depends on your risk appetite, your jurisdiction and your product, which makes it a business decision rather than a model output. That is component three doing its job.
What to build before launch, not after the first case:
- Map every tag your configuration can produce to one of four outcomes: auto-approve, auto-deny, request more information, or human review.
- Write the mapping down as a procedure your support, risk and compliance people actually follow. An undocumented mapping becomes a different decision every time.
- Decide who can override a result, and record the override against the
scanRefwith a reason. Overriding a fraud tag with no audit trail is the finding you do not want in an audit. - Handle both resolution paths. A reviewer can clear a tag in the dashboard, which re-evaluates the status automatically. Your integration can reactivate a token for a proof-of-address upload, a risk assessment or a questionnaire, and nothing else. Neither path resubmits a primary ID document: that needs a new session.
Treating every SUSPECTED as a denial rejects legitimate customers over explainable mismatches. Treating them all as approvals removes the point of running the checks.
The component that breaks quietly is the one with no screen. An identity verified in March is not verified in September: sanctions designations change, politically exposed status changes, and documents expire. Component one's screening leg is continuous, not a one-time gate.
Practical shape:
- Re-screening. Set
checkAmlon the session so the verified identity enters monitoring, and build a handler for status-change alerts on customers you already onboarded. - Re-verification. Trigger on risk events rather than on a calendar: a document expiry date you already hold, a change of address, a jurisdiction change, unusual activity, a step up in account limits. Face authentication re-checks a returning user against an earlier session's
scanRefwithout a full re-verification. - Transaction monitoring. Rules tuned to your own risk assessment, alerts routed to a named owner, and a filing path for suspicious activity reports. Rules that nobody tunes produce alert volumes nobody reads, which is worse than no rules because it looks like coverage.
- Records. Keep the verification evidence for your jurisdiction's retention period, joined to the
scanRef. Retention is configurable on the platform side; the obligation to know your own period is yours.
KYC verification is billed per verification, and the configuration you chose above is what determines the number. Against iDenfy's published rates as of August 2026, the base pay-as-you-go rate is $1.35 per verification with a $135 monthly minimum, and the checks you switch on in the session payload each carry a published add-on: 3D liveness at $0.20, duplicate detection at $0.10, sanctions and PEP screening at $0.45, proof of address at $0.90, IP proxy risk at $0.12, and 24/7 human review at $0.45. Billing only for approved verifications is itself a $0.50 add-on on those plans, with volume-based rates on enterprise contracts.
Two things follow. A high-assurance session is not double the base rate; it is the base plus a handful of cents-level checks. And the expensive add-ons are the ones driven by regulation rather than by fraud, which means your sector, not your threat model, usually sets your unit cost. Current numbers are on the pricing page, and a 14-day trial covers integration work before any of it applies.
| File | What it does |
|---|---|
| examples/create_session.sh | curl, minimal and fully configured session payloads |
| examples/create_session.py | Python, standard library only, prints the redirect URL |
| examples/create_session.js | Node 18+, fetch, no dependencies |
| examples/webhook_receiver.py | HMAC-verified webhook endpoint with a worked status handler |
| examples/webhook_verify.js | Signature verification in Node, constant-time |
| examples/lookup_status.sh | Status and data retrieval by scanRef |
Every example reads credentials from the environment and never writes them anywhere. Test against the dashboard sandbox before you point anything at real users.
This repo covers how to implement KYC verification, not what your obligations are. Your business is the regulated entity under the applicable KYC and AML regime, and the requirements depend on your jurisdiction, your sector and your risk profile. Verification software provides the checks your programme runs; it does not make you compliant, and no vendor certification transfers your obligations. iDenfy is audited under ISO/IEC 27001:2022 and SOC 2 Type II and holds an eIDAS Declaration of Conformity, which supports your programme rather than replacing it. Confirm your requirements with a qualified compliance professional.
The process of identifying and verifying a customer at onboarding, assessing the financial-crime risk they carry, and continuing to monitor both while the relationship lasts. It resolves into three components: a Customer Identification Program, transaction monitoring, and risk management. Document and biometric checks sit inside the first component; on their own they are not a KYC programme.
Identity verification is the step that establishes who somebody is, usually a document check plus a biometric match. KYC verification is the wider obligation that step belongs to, which also covers screening against sanctions and politically exposed person lists, risk scoring, ongoing monitoring and record keeping. Buying only the identity step and calling it KYC is the most common gap in early-stage onboarding flows.
Establish identity from a document and a live biometric, compare the result against the data you already hold, screen the verified identity against sanctions, PEP and adverse media sources, produce a status your code can act on, keep monitoring after onboarding, and leave an audit trail joined to one identifier. Practically it also needs an answer for borderline cases, because the alternative is a threshold that either rejects genuine customers or lets forgeries through. iDenfy's KYC software runs these on one session with a 24/7 in-house review team on the middle band.
Check five things: document and country coverage against your actual markets rather than a global count, who reviews borderline cases and at what price, whether screening and business verification run on the same session and audit trail, whether webhooks are signed and retried, and what a configured verification costs once the add-ons your sector requires are switched on. Ask for the share of sessions that route to human review on flows like yours, since that tail is what users experience as slow.
No. ID verification is one control inside an AML programme. The programme also needs screening, a risk-based approach with customer risk profiles, ongoing monitoring of both customers and transactions, escalation to enhanced due diligence for higher-risk cases, record keeping, and reporting of suspicious activity. KYC is the part of AML that happens at and after onboarding, not a substitute for the rest.
Regulated entities, and the list is wider than banks: fintechs and payment providers, crypto platforms and virtual asset service providers, lenders and credit unions, iGaming operators, forex and trading platforms, real estate and mortgage firms, marketplaces handling payments, and money service businesses. The specific rules come from your jurisdiction, such as the BSA and FinCEN guidance in the US or the anti-money-laundering directives in the EU. REQUIREMENTS.md has the data-collection floor and the jurisdiction pointers.
The automated path resolves in seconds, and iDenfy publishes an average of 60 seconds per user across its flow. Cases routed to human review take minutes. Design for the second number, because it is the one that shapes your onboarding funnel and your support load.
Corrections to the code, the status handling or the requirements list are welcome. Keep every example dependency-free, and cite the documentation page for any API claim. See CONTRIBUTING.md.
The three-component framing was adapted from iDenfy's guide to KYC verification, and every request and field name is taken from the iDenfy API documentation. The integration sequence, the status state machine and the code here are our own. iDenfy is the verification vendor; the regulated entity is you.
MIT. See LICENSE.