Document Verification: Passport, License, ID Card Turnkey
Financial and car-sharing services lose up to 30% of clients at the KYC stage due to poor verification. Typical problem: the system rejects real documents or passes fakes. Central Bank data indicates that up to 15% of forgeries go undetected by single-layer OCR. That means direct financial losses and reputational risks. Over the past years, we have developed an approach that minimizes such scenarios — verification accuracy exceeds 98% with a false rejection rate below 1%.
Document verification is the task of checking the authenticity of a physical document based on its digital image. It is used in KYC processes (banks, fintech, car-sharing, mobile operators). The task is more complex than simple OCR: you need not only to read the data but also to confirm its authenticity. Errors at this stage lead to direct financial losses and reputational risks.
How to Ensure Verification Accuracy Above 98%?
Single-layer OCR is not enough. Modern forgeries contain machine-readable zones that are visually indistinguishable from real ones. Therefore, we use a cascade of checks, each filtering a certain class of fraud. According to Central Bank data, up to 15% of forgeries are not detected by single-layer OCR — our pipeline reduces this figure to 1%.
class DocumentVerificationSystem:
def __init__(self):
self.ocr = PaddleOCR(use_angle_cls=True, lang='ru')
self.mrz_reader = MRZReader()
self.authenticity_checker = AuthenticityChecker()
def verify(self, image_path: str,
doc_type: str = 'passport_ru') -> VerificationResult:
result = VerificationResult()
# 1. Image quality
quality = self.assess_image_quality(image_path)
if quality.score < 0.6:
result.rejected = True
result.reason = 'low_image_quality'
return result
# 2. OCR all visible fields
result.ocr_fields = self.extract_fields(image_path, doc_type)
# 3. MRZ (Machine Readable Zone) for passports
if doc_type in ['passport_ru', 'foreign_passport']:
mrz_data = self.mrz_reader.read(image_path)
result.mrz_data = mrz_data
# Cross-check MRZ vs OCR fields
result.mrz_consistency = self.cross_check_mrz(
result.ocr_fields, mrz_data
)
# 4. Security features check
result.security_features = self.authenticity_checker.check(image_path)
# 5. Final verdict
result.verified = self._make_verdict(result)
return result
Why Is Multi-Level Document Check Critical for KYC?
Each level solves its own task: image quality detection filters blurry photos, MRZ checks data integrity, and analysis of security elements (guilloches, microtext, UV glow) detects forgeries. In practice, this gives accuracy >98% with a false rejection rate below 1%. For comparison, standard OCR solutions without MRZ validation yield about 85% accuracy on real data.
How Do We Read MRZ and Check Checksums?
MRZ is a standardized zone at the bottom of the passport with encoded data. Checksums allow verifying data correctness. The passporteye library gives a baseline accuracy of about 95%, but after our refinement with post-processing and cross-validation, it reaches 99%+. Our processing pipeline is 3 times faster than standard solutions thanks to GPU optimization (batch inference on Triton Server).
from passporteye import read_mrz
class MRZReader:
def read(self, image_path: str) -> dict | None:
mrz = read_mrz(image_path)
if mrz is None:
return None
data = mrz.to_dict()
return {
'surname': data.get('surname', ''),
'names': data.get('names', ''),
'country': data.get('country', ''),
'number': data.get('number', ''),
'nationality': data.get('nationality', ''),
'date_of_birth': data.get('date_of_birth', ''),
'sex': data.get('sex', ''),
'expiry_date': data.get('expiry_date', ''),
'personal_number': data.get('personal_number', ''),
'valid_composite': data.get('valid_composite', False),
'valid_number': data.get('valid_number', False),
'valid_dob': data.get('valid_dob', False),
'valid_expiry_date': data.get('valid_expiry_date', False),
}
What About Expiry Date and Format Check?
def validate_passport_fields(fields: dict) -> dict:
"""Check Russian passport field formats"""
errors = []
# Series and number: 4 digits series, 6 digits number
if fields.get('series'):
if not re.match(r'^\d{4}$', fields['series']):
errors.append({'field': 'series', 'error': 'invalid_format'})
if fields.get('number'):
if not re.match(r'^\d{6}$', fields['number']):
errors.append({'field': 'number', 'error': 'invalid_format'})
# Issue date: not later than today, not earlier than 2000 (acceptable for existing documents)
if fields.get('issue_date'):
issue_date = parse_date(fields['issue_date'])
if issue_date:
from datetime import date
if issue_date > date.today():
errors.append({'field': 'issue_date', 'error': 'future_date'})
if issue_date.year < 2000:
errors.append({'field': 'issue_date', 'error': 'too_old'})
# Date of birth
if fields.get('birth_date'):
birth_date = parse_date(fields['birth_date'])
if birth_date:
age = (date.today() - birth_date).days / 365
if age < 14 or age > 120:
errors.append({'field': 'birth_date', 'error': 'invalid_age'})
return {'valid': len(errors) == 0, 'errors': errors}
How to Set Up a Verification Pipeline in 5 Steps?
- Requirements gathering — analyze document types, acceptable image formats, speed requirements.
- Component selection — OCR (PaddleOCR, Tesseract), quality detector, MRZ reader (passporteye), authenticity check module.
- Pipeline development — implement
DocumentVerificationSystemclass with cascade checks, integrate GPU inference. - Integration — REST API or gRPC endpoint, support for batch requests for high throughput.
- Testing and monitoring — run on your data (at least 1000 samples), record metrics (latency p99, accuracy, false positives).
Case Study: Bank with 1M Clients Reduced KYC Time from 10 Minutes to 30 Seconds
After implementing our pipeline, the client achieved a one-time verification in 20 seconds, and repeat verification in 2 seconds (using result caching). The false rejection rate dropped from 12% to 1.5%, reducing operator workload by 8x. Contact us to achieve similar results — reduce operational costs by up to 45% through automation. We will estimate your project in 2 days.
Supported Document Types and Implementation Timelines
| Document | Extracted Fields | MRZ |
|---|---|---|
| Russian Passport | Series, number, full name, date of birth, gender, place of birth, issue date, issued by | No (internal) |
| Russian International Passport | Full name, number, date of birth, expiry date | Yes |
| SNILS | Number, full name, date of birth | No |
| Driver's License | Series/number, categories, full name, expiry date | No |
| INN | Number | No |
| Task | Timeline |
|---|---|
| Verification of 1 document type | 3–4 weeks |
| Full KYC (passport + SNILS + photo) | 5–8 weeks |
| Anti-fraud verification | 7–12 weeks |
Checklist of Typical Implementation Mistakes
- Not checking image quality before OCR — degrades results by 30%.
- Ignoring MRZ validation: 20% of forgeries pass through simple OCR solutions.
- Not setting a timeout during integration — clients leave when delays exceed 3 seconds.
- Forgetting to test edge cases: rotation >15°, shadows, glare.
- Not recording metrics — impossible to improve the pipeline.
What's Included in Turnkey Work?
We provide the full cycle: requirements analysis, architecture selection (RAG, classifier, or hybrid), pipeline implementation, REST API integration, testing on your document pool (at least 1000 samples), documentation, operator training, and 1 month post-launch support. We guarantee verification accuracy of at least 98% with a false rejection rate below 1%. Typical ROI is 6–9 months.
Our experience includes 15+ projects in fintech, banking, and government sectors. Order a free pilot — we will verify 100 of your documents and show the result. Get demo access to an already running system.







