How to use this page
This page walks you through building one product, start to finish. The Identity module explains why verification is hard. The build sheet lists every tool with prices. This page tells you which eight steps there are and how to connect them.
It is written for someone who has not built this before.
Read this before anything else. Video KYC in India is not a video call with some checks bolted on. It is a regulated process where the product is the compliance. Build the video first and the controls later and you will rebuild it. Several of the requirements below — a proprietary app, infrastructure in your own premises, a concurrent audit before activation — are architecture decisions, not features.
What Video KYC actually is
Video KYC lets you open an account for someone who never walks into a branch. A trained officer of your firm talks to the customer on a live video call, software reads their documents, matches their face, and confirms a real person is present.
In India it is called V-CIP — Video-based Customer Identification Process — and it sits in Para 19 of the RBI KYC Master Direction.
Why it matters commercially: a compliant V-CIP session is treated as face-to-face. That is only true of three things — a physical meeting, digital KYC with a physical meet, and V-CIP. It means full CDD, no enhanced-due-diligence classification, and none of the transaction caps that apply to OTP-based eKYC.
What it is not:
- Not a Zoom call. The application must be proprietary. Zoom, Teams and Meet are explicitly out.
- Not a liveness check with video attached. The human officer is a required control, not a cost to be automated away.
- Not finished when the call ends. A separate team must audit the session before the account is activated.
The whole journey, in one table
Eight steps. Read this once and the rest is detail.
| # | Step | In plain words |
|---|---|---|
| 1 | Can they even start? | Right device, in India, real connection. Check before you waste an officer's time. |
| 2 | Get consent, on the record | Recorded, timestamped, and impossible to alter later. |
| 3 | Capture the documents | Live capture. An uploaded scan does not count. |
| 4 | Prove a real person is there | Not a photo, not a video, not a deepfake, not a virtual camera. |
| 5 | Match the face to the document | Is the person on the call the person on the ID? |
| 6 | The conversation | A trained officer asks questions that cannot be rehearsed. |
| 7 | Decide | Approve, re-do, or refuse. Three outcomes, never two. |
| 8 | Audit, then activate | A different team checks the session. Only then does the account open. |
Steps 1, 2 and 8 are the ones teams skip, and they are the ones an inspection looks at. A build that does 3–6 beautifully and skips 8 is not a video KYC product — it is a video call with face matching.
Steps 1 and 2 — can they start, and consent
Step 1 — Can they even start?
What it does: confirms the customer is in India, on a real connection, with a working camera, before anyone joins the call.
| Option | What it is | Effort | Pick this when |
|---|---|---|---|
| A. Browser APIs only | Geolocation API plus a getUserMedia test. | A day | Start here. It catches the honest failures, which are most of them. |
| B. Add IP intelligence | A lookup service that tells you country, ASN, and whether the IP is VPN, proxy or hosting. | Days, small per-lookup fee | As soon as you are live. Required to detect spoofing. |
| C. Full device intelligence | Device fingerprint, emulator detection, rooted-device checks. | Weeks, licensed | Volume is high enough that organised attempts are worth defending against. |
from ipaddress import ip_address
# RBI requires the customer to be IN INDIA, the app to reject foreign IPs, and
# to detect IP spoofing. Check all of it BEFORE an officer joins the call --
# an officer's time is the most expensive thing in this pipeline.
def preflight(session):
fail = []
# 1. GEOTAG. Live GPS from the device, not an IP-derived guess.
gps = session.get("gps")
if not gps:
fail.append("no_gps") # permission denied or spoofed
elif not (6.0 <= gps["lat"] <= 37.5 and 68.0 <= gps["lon"] <= 97.5):
fail.append("outside_india") # rough India bounding box
# 2. IP. Must resolve to India, and must not look like a tunnel.
ip = session.get("ip")
if not ip or session["ip_country"] != "IN":
fail.append("foreign_ip")
if session.get("is_vpn") or session.get("is_proxy") or session.get("is_hosting"):
fail.append("ip_spoof_suspected") # datacentre ASN on a phone = no
# 3. DO THE TWO AGREE? GPS in Mumbai and IP in Frankfurt is the
# interesting case, and it is the one a spoofer produces.
if "outside_india" not in fail and "foreign_ip" not in fail:
if session.get("ip_distance_km", 0) > 500:
fail.append("gps_ip_mismatch")
# 4. Can the call actually work? A session that dies at step 6 wastes the
# officer AND the customer, and they rarely come back.
if session.get("bandwidth_kbps", 0) < 400: fail.append("bandwidth_too_low")
if not session.get("camera_ok"): fail.append("no_camera")
if not session.get("mic_ok"): fail.append("no_mic")
return {"proceed": not fail, "reasons": fail}
# WHAT TO CHECK
# [ ] GPS is captured live at the START and recorded INTO the video, not read
# once and stored beside it. The recording itself must carry the coordinates
# [ ] tell the customer WHICH check failed, in plain words. "Please enable
# location" gets you a session; "Verification failed" gets you a complaint
# [ ] a bounding box is a coarse first filter, not a border. Use it to reject
# obvious cases fast, and a proper geocoder for the record
# [ ] datacentre and hosting ASNs on a consumer session are the strongest
# spoofing signal you get. Treat hosting-provider IPs as disqualifying
# [ ] failures are LOGGED with the reason. The distribution tells you whether
# you have a fraud problem or a UX problem, and they look identical in a
# total
# [ ] never let a failed pre-flight silently fall back to a different KYC route.
# That decision is a compliance decision, not an error handler
Connect it to step 2: a session that passes gets a session id and a recording handle. Everything after this writes into that one recording — consent, documents, face, the conversation. One file, one timeline, one set of timestamps.
Step 2 — Consent, on the record
What it does: captures the customer agreeing, in a form you cannot later be accused of editing.
The requirement has three parts and teams usually build one: consent must be recorded, auditable, and alteration-proof.
| Option | What it is | Pick this when |
|---|---|---|
| A. Spoken, inside the recording | The officer asks, the customer says yes, it is in the video. | Always. This is the baseline and it satisfies “recorded”. |
| B. Hash the recording | Store a cryptographic hash of the file when the session closes. | Always. This is what makes “alteration-proof” a fact rather than a claim. |
| C. External timestamping | A trusted timestamp authority signs the hash. | When you need to prove the file existed at a time, not just that it is unchanged. |
The gotcha nobody documents: “alteration-proof” is usually implemented as we do not have a feature that edits it. That is not the same thing and it will not survive a question from an auditor. Hash the file on close, store the hash somewhere the video pipeline cannot write to, and re-verify it on retrieval. It costs almost nothing and it converts a policy claim into arithmetic.
Steps 3 and 4 — documents, and proving a real person
Step 3 — Capture the documents
What it does: gets the identity documents into the session, live.
Live capture is mandatory. An uploaded scan does not meet the standard, however clear it is. The document has to be shown to the camera during the session.
| Route | What it gives you | Note |
|---|---|---|
| DigiLocker / offline Aadhaar XML | The issued record, already verified. | XML or QR must be no older than three days. |
| Live PAN capture | An image verified against the authorised database. | Verification against the database, not just a reading of the card. |
| Other OVD via live capture | For customers without the above. | Reading and structuring is Document AI — the same eight steps, inside this one. |
The Aadhaar number must be redacted in your records. Not masked in the UI — redacted in what you store. Teams capture a clean frame of the Aadhaar card into the video recording and then discover the recording itself now contains the full number, retained for years. Decide where redaction happens before you record anything, because you cannot un-record.
Step 4 — Prove a real person is there
What it does: confirms the face on the call belongs to a living human who is actually present.
# Liveness has three generations, and most products are stuck in the first two.
#
# 2019: is this a PHOTO? -- beaten by playing a video
# 2021: is this a VIDEO REPLAY? -- beaten by a deepfake
# 2026: is this a DEEPFAKE, and is it even coming from the CAMERA?
#
# The 2026 question is INJECTION. An attacker does not hold a screen up to the
# lens -- they replace the camera feed with a virtual camera driver and send a
# synthetic face straight into your app. Every pixel-level liveness check in the
# world passes, because the pixels are perfect.
def assess_liveness(signals):
checks = {
# presentation attack: something held up to a real camera
"pad_score": signals["pad_score"] >= 0.90,
# injection attack: the frames never came from a camera at all
"camera_is_real": signals["virtual_camera_detected"] is False,
"driver_signed": signals["camera_driver_trusted"],
"frame_timing_ok": signals["frame_interval_variance"] > 0.0001,
# a synthetic stream is often TOO regular. Real cameras jitter.
"sensor_noise_ok": signals["sensor_noise_present"],
}
failed = [k for k, v in checks.items() if not v]
if "camera_is_real" in failed or "driver_signed" in failed:
return {"outcome": "reject_hard", "reason": "injection_suspected",
"escalate": True}
if failed:
return {"outcome": "retry", "reason": failed, "attempts_allowed": 2}
return {"outcome": "pass"}
# WHAT TO CHECK
# [ ] ASK EVERY VENDOR: "do you detect virtual-camera injection?" The answer
# tells you whether you are buying a 2021 product or a 2026 one. It is the
# single most useful question in this whole procurement
# [ ] ask for iBeta PAD certification and the LEVEL. Level 1 is photos and
# screens; Level 2 is masks. Neither covers injection -- that is separate
# [ ] DO NOT require blinking or smiling. RBI's FAQ (Q20) says specific facial
# gestures are not mandatory, and accommodation is required for customers
# who cannot perform them. A gesture-gated flow excludes disabled customers
# and is a conduct problem as well as an accessibility one
# [ ] an injection signal is a HARD reject and an escalation, not a retry. A
# retry just tells the attacker which check fired
# [ ] log the raw scores, not the verdict. Thresholds change; the evidence of
# what you saw should not have to be recomputed
# [ ] rehearse a failure. If your liveness vendor is down, what happens? The
# answer must not be "approve anyway"
The gotcha nobody documents: injection. Every liveness product sold before about 2023 answers the question “does this look like a real face?”. A virtual camera makes that question meaningless, because the synthetic face is a perfect one. The question that matters in 2026 is “did these frames come from a physical camera on this device?” — and it is answered with driver checks, frame-timing analysis and sensor-noise detection, not with a better face model.
Steps 5 and 6 — face match and the conversation
Step 5 — Match the face to the document
What it does: compares the live face with the photo on the identity document.
| Option | What it is | Pick this when |
|---|---|---|
| A. Vendor API | Send both images, get a similarity score. | Almost always. This is a commodity and not where your effort belongs. |
| B. Self-hosted model | Run face matching on your own infrastructure. | Residency rules make sending faces out impossible, or volume justifies it. |
The technical work is small. The policy work is not, and it is yours:
- Set the threshold deliberately, and write down why. It trades false accepts against false rejects and both are harms.
- Test on your own population. Face matching accuracy varies by skin tone, age and gender across every published evaluation. A threshold tuned on a vendor's demo set is a threshold tuned on somebody else's customers.
- A low score is a human decision, not an automatic refusal. The officer is on the call already. That is the point of them.
Step 6 — The conversation
What it does: a trained officer of your firm talks to the customer and forms a judgement.
This step cannot be automated away. The officer must be your trained official, must run a randomised set of questions so nothing can be pre-rehearsed, and must be able to act on anything that looks wrong.
Design the question bank as data, not as a script in someone's head. A pool of questions, drawn at random, with the drawn set recorded against the session. Then “were the questions randomised” has an answer you can produce, and a coached applicant cannot be fed the list. This is cheap to build on day one and awkward to retrofit.
Give the officer a single screen with everything already on it: the document reading, the face match score, the liveness result, the pre-flight signals. An officer hunting through tabs during a live call is the most common cause of a rushed judgement.
Steps 7 and 8 — decide, audit, activate
Steps 7 and 8 — Decide, audit, then activate
from datetime import datetime, timedelta, timezone
IST = timezone(timedelta(hours=5, minutes=30))
# THREE OUTCOMES. A two-outcome system either rejects good customers or
# approves bad sessions, and usually both.
def officer_decision(session, checks, officer):
if checks["liveness"]["outcome"] == "reject_hard":
return {"outcome": "reject", "reason": "injection_suspected",
"reviewable": True} # a person can still overturn it
if not checks["documents_valid"] or checks["face_match"] < session["threshold"]:
return {"outcome": "redo", "reason": "quality_or_match",
"guidance": "explain WHICH part, in plain words"}
return {"outcome": "recommend_approve", "by": officer["id"],
"at": datetime.now(IST).isoformat()}
# STEP 8. The officer RECOMMENDS. A DIFFERENT team approves. The account does
# not exist until that second team has looked. This is a hard requirement and
# it is the step product teams discover three weeks before launch.
def concurrent_audit(session, auditor):
assert auditor["team"] != session["officer_team"], "auditor must be independent"
findings = []
for need in ("recording_present", "gps_in_recording", "timestamps_present",
"consent_captured", "officer_credentials_recorded",
"questions_randomised", "aadhaar_redacted", "hash_matches"):
if not session.get(need):
findings.append(need)
return {"activate": not findings, "findings": findings,
"auditor": auditor["id"], "at": datetime.now(IST).isoformat()}
# WHAT TO CHECK
# [ ] the auditor is a DIFFERENT TEAM. Asserted in code, not in a policy
# document. This is the most commonly faked control in the whole process
# [ ] account activation is gated on the audit result. If an account can exist
# before the audit, the audit is decorative
# [ ] "redo" tells the customer which part failed. A blank retry produces the
# same failure and then an abandoned application
# [ ] the hash from step 2 is re-verified HERE, at audit time. Checking it only
# at write time proves nothing about the intervening days
# [ ] audit capacity is planned as a number. It is a person per N sessions and
# it does not scale by deploying more servers -- this is the line that
# silently caps your onboarding volume
# [ ] measure the audit FAILURE rate weekly. Near zero means the audit is a
# rubber stamp; rising means something upstream broke
# [ ] rejections are reviewable by a human on request. "The system said no" is
# not an answer you can give a customer about their bank account
The gotcha nobody documents: the concurrent audit is a capacity constraint, not a feature. Every session must be reviewed by an independent team before the account activates. That is a person, reviewing recordings, at some rate per hour. Your onboarding throughput is capped by that number and no amount of infrastructure changes it. Teams model gateway costs and liveness costs carefully and then discover the ceiling on launch week. Work out your audit rate before you forecast volume.
What it costs
Video KYC — what it costs
Verified September 2026Model cost per COMPLETED onboarding, not per session. Sessions fail — bad connections, failed liveness, documents that will not read, customers who drop. If 30% of sessions do not complete, your real cost per customer is the session cost divided by 0.7, plus the officer and auditor time spent on the ones that failed. That is a very different number from the vendor's per-verification rate.
Three versions you could build
The two-week version
Build: browser pre-flight → a bought V-CIP platform that handles video, liveness, capture and face match → your own officer console → a simple audit queue → Postgres for the evidence record.
You get: something compliant enough to test with real customers under supervision.
It breaks when: volume passes what one auditor can review in a day.
The proper version — start here if you are serious
Build: everything above, plus — IP intelligence with spoof detection → injection detection confirmed with the vendor in writing → a randomised question bank as data → hashing at session close and re-verification at audit → an officer console with every signal on one screen → audit capacity modelled as a number → weekly metrics on completion rate, audit failure rate and reason distribution.
Trade: you own the orchestration, the evidence and the thresholds. That is the right trade — those three are what an inspection examines, and none of them is something a vendor can hold for you.
The enterprise version
Build: everything above, plus — self-hosted face match and liveness for residency → device intelligence → a dedicated audit function with its own tooling → full model governance on the matching thresholds.
It breaks when: you build it before you have run a thousand real sessions and learned where your own failures actually are.
If you take one thing from this page: work out your concurrent-audit rate in week one. Everything else on this page can be bought, tuned or fixed later. That number is a hard ceiling on how many customers you can onboard per day, it is set by people rather than technology, and almost nobody discovers it until they need the capacity.
What goes wrong
| What goes wrong | Why | Fix |
|---|---|---|
| Liveness passes a deepfake | The product answers “is this a real face”, not “did this come from a camera”. | Injection detection. Ask the vendor the question directly. |
| The recording contains an un-redacted Aadhaar | Redaction was designed for the UI, not for the stored video. | Decide redaction before you record. You cannot un-record. |
| Onboarding volume hits a wall | The concurrent audit is one person per N sessions. | Model audit capacity before forecasting volume. |
| Disabled customers cannot complete | The flow requires blinking or smiling. | Not mandatory per the RBI FAQ. Remove the gesture gate. |
| “Alteration-proof” cannot be demonstrated | It meant “we have no edit feature”. | Hash on close, store it out of reach, re-verify at audit. |
| Sessions die at step 6 | No bandwidth check at step 1. | Pre-flight. The officer's time is the expensive part. |
| Audit failure rate is near zero | It is a rubber stamp. | Sample and re-review. A control nobody fails is not a control. |
Where to go next
Document AI
Step 3 of this page is that whole product, nested inside this one. Eight steps, same shape.
Identity & Onboarding
Every tool with prices, including the face-match and liveness vendors.
AML & Compliance
Screening runs on the name this process captured. Wrong name in, wrong screen out.
India regulation
The KYC Master Direction, retention rules and what counts as face-to-face.
This page is a guide, not a specification. V-CIP is a regulated process and a non-compliant onboarding is a supervisory matter, not a bug. Nothing here is legal advice. Have your flow, your retention and your audit process reviewed by qualified counsel and your compliance officer before a real customer joins a call.
Sources
Every figure, rule and date on this page, and where to check it. Entries are typed so you can see which are primary-sourced and which are industry reporting.
- officialRBI KYC Master Direction, Para 19 — the V-CIP framework: trained official, live recording, geo-tagging, liveness, IP control, concurrent audit and the face-to-face equivalence. www.rbi.org.in
- officialRBI sector-specific KYC Master Directions, 28 November 2025 — the replacement of the 2016 Direction and the extension of V-CIP to ten institution types including payment aggregators. www.rbi.org.in
- officialRBI KYC FAQs — Q20 — specific facial gestures such as blinking or smiling are not mandatory for the liveness check, with accommodation required. www.rbi.org.in
- officialUIDAI — offline Aadhaar XML and QR, the three-day freshness limit, and the redaction requirement. uidai.gov.in
- officialDigiLocker — issued-document retrieval as an OVD route inside the session. www.digilocker.gov.in
- industryV-CIP platform pricing and capability reporting — the ~₹10 per verification entry point, iBeta PAD levels, and the injection-detection question. Vendor-published and third-party compiled; confirm in writing.
Checked September 2026. V-CIP rules changed materially in 2025; the date is part of the claim.
Ask an AI about this page
Opens your assistant with this page as the source, and a question rather than a summary. It will ask what you are building before it answers.
Nothing is sent from here. The link carries only this page’s title and address.