How to Design an Identity Verification & KYC Platform
Building a Production-Grade Know Your Customer Engine — Document Verification, Biometrics, AML Compliance
1. Introduction & Why KYC is Hard
Identity verification is the backbone of trust in the digital economy. Every time a user opens a bank account, applies for a loan, purchases cryptocurrency, or registers on a regulated platform, the provider must verify that the person is who they claim to be — and that they are not involved in money laundering, terrorism financing, or fraud. This process, broadly called Know Your Customer (KYC), is legally mandated in over 190 countries under anti-money laundering (AML) regulations enforced by bodies like the Financial Action Task Force (FATF), FinCEN (US), the FCA (UK), and the EU's AMLD directives. Failure to comply can result in fines exceeding $1 billion — as seen with HSBC's $1.9 billion settlement in 2012 or Westpac's $1.3 billion penalty in 2020.
Building a KYC platform is not just a compliance checkbox — it is a complex engineering challenge spanning computer vision, machine learning, distributed systems, document forensics, and regulatory technology. The system must verify government-issued identity documents from over 195 countries (each with unique layouts, security features, and languages), detect sophisticated fraud attempts (Photoshopped documents, deepfake videos, printed screen captures), perform real-time biometric matching between a selfie and an ID photo, screen individuals against global sanctions lists (OFAC, UN, EU), calculate AML risk scores, and do all of this in under 30 seconds while meeting strict data privacy regulations (GDPR, CCPA, PIPL). The platform must be accurate (false rejection rates below 2% for legitimate users), fast (under 30 seconds for 90% of verifications), and secure (biometric data encrypted at rest with AES-256 and in transit with TLS 1.3).
The complexity deepens when you consider the global nature of identity. A passport from Japan looks nothing like one from Nigeria. A driver's license from Germany has different security features than one from Brazil. Some countries use biometric passports with RFID chips, others use paper laminated cards. Some IDs have MRZ (Machine Readable Zone) codes, others have QR codes, and some have neither. The platform must handle all of these variations while maintaining consistent accuracy and speed. Additionally, fraudsters constantly evolve their techniques — from simple Photoshop edits to sophisticated AI-generated deepfakes and forged holograms — requiring the platform to continuously update its fraud detection models.
Real-world KYC platforms operate at massive scale. Jumio, a leading identity verification provider, processes over 1 million verification attempts per day across 200+ countries. Onfido handles 20 million checks per month for companies like Revolut and Zipcar. Stripe Identity processes verifications for millions of businesses on the Stripe platform. These platforms must maintain sub-30-second response times, 99.9%+ uptime, and sub-1% false rejection rates while scaling to handle traffic spikes (such as a crypto exchange during a bull market). Building such a platform requires careful engineering across every layer of the stack, from the mobile SDK that captures the document photo to the ML pipeline that analyzes it to the compliance engine that makes the final decision.
Real-World Case Studies
| Company | System | Scale | Key Innovation |
|---|---|---|---|
| Jumio | Identity verification platform | 1M+ checks/day | AI-powered document + biometric verification, liveness detection |
| Onfido | Identity verification SDK | 20M checks/month | Facial biometrics with ML, smartphone-based document scanning |
| Stripe Identity | Embedded verification | Millions of businesses | API-first design, pre-built UI components, multi-document support |
| Trulioo | Global identity verification | 5B+ data points | 500+ data sources across 100+ countries, real-time database checks |
| Sumsub | Verification & AML platform | Millions of users | Combined KYC + AML + transaction monitoring, modular API |
Jumio's approach is particularly instructive: they combine document verification (analyzing the physical document for authenticity), biometric verification (matching the user's face to the document photo), and liveness detection (ensuring the user is physically present, not a photo or video replay) into a single orchestrated flow. Their key innovation is the "identity cube" — a 3D model of the document constructed from multiple images to detect thickness anomalies, lamination issues, and other physical tampering indicators.
Onfido pioneered the concept of "smart document capture" — using the smartphone's camera with real-time ML guidance to ensure the user captures a high-quality image of their document. The SDK provides feedback like "move closer", "reduce glare", "center the document" to optimize the capture quality before it's even sent to the server. This dramatically improves the first-attempt success rate and reduces the need for manual review.
2. Functional & Non-Functional Requirements
Functional Requirements
- Document Verification: Accept and verify government-issued identity documents (passports, national ID cards, driver's licenses) from 190+ countries. Extract text data via OCR and verify document authenticity using AI models.
- Biometric Verification: Capture a live selfie from the user and match it against the photo on their identity document using face recognition models. Support liveness detection to prevent spoofing attacks.
- Watchlist Screening: Screen individuals against global sanctions lists (OFAC SDN, UN Security Council, EU Consolidated List), Politically Exposed Persons (PEP) databases, and adverse media sources.
- AML Risk Scoring: Calculate a composite risk score based on document authenticity, biometric match confidence, watchlist matches, address risk, and country risk factors.
- KYC Workflow Orchestration: Manage the end-to-end verification workflow from document collection through verification to final approval/rejection, with manual review queues for edge cases.
- Ongoing Monitoring: Re-verify customers periodically and when risk indicators change. Monitor for document expiry, new sanctions matches, and adverse media.
- Multi-Tenant SDK: Provide embeddable JavaScript and mobile SDKs for web and native app integration with customizable UI.
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Verification Latency (automated) | < 30 seconds for 90% of attempts | User experience — long waits cause abandonment |
| False Accept Rate (FAR) | < 0.1% | Regulatory requirement — never let a fraudster through |
| False Rejection Rate (FRR) | < 2% | Revenue impact — legitimate users must not be blocked |
| Liveness Detection Accuracy | > 99% attack detection | Critical for preventing spoofing attacks |
| Document Coverage | 190+ countries, 10,000+ document types | Global platform must handle diverse identity documents |
| Availability | 99.99% | Downtime blocks customer onboarding |
| Data Encryption | AES-256 at rest, TLS 1.3 in transit | Biometric data requires highest security standards |
| Data Retention | Configurable per jurisdiction (default 5 years) | AML regulations require 5-year record retention |
| Manual Review SLA | < 4 hours for escalations | Customers should not wait more than 4 hours |
| API Rate Limit | 1000 req/min per client | Protect platform from abuse |
| GDPR Compliance | Right to erasure within 72 hours | European data protection requirement |
| SOC 2 Type II | Annual audit | Enterprise customer requirement |
Key Design Tradeoffs
| Tradeoff | Option A | Option B | Our Choice |
|---|---|---|---|
| On-device vs Server-side | On-device (faster, privacy-friendly, limited accuracy) | Server-side (higher accuracy, higher latency) | Hybrid — on-device capture guidance + server-side ML |
| Synchronous vs Async | Synchronous (simpler, user waits) | Async (better UX, complex callback handling) | Async with webhook callbacks + polling |
| Custom ML vs Third-party API | Custom ML (full control, high upfront cost) | Third-party API (fast to market, ongoing cost) | Third-party for core (Jumio/Onfido), custom for niche |
| Single database vs Multi-region | Single region (simpler, GDPR issues) | Multi-region (compliant, complex) | Multi-region with data residency controls |
3. High-Level Architecture Overview
The identity verification platform consists of seven main components: the Client SDK (captures documents and selfies), the API Gateway (routes requests and enforces rate limits), the Verification Engine (orchestrates the multi-step verification pipeline), the ML Services (document analysis, face matching, liveness detection), the Compliance Engine (watchlist screening, AML risk scoring), the Review Dashboard (manual review queue for edge cases), and the Data Store (encrypted document images, verification results, audit logs). These components communicate through an event-driven architecture using Kafka for asynchronous processing and gRPC for synchronous ML inference calls.
Request Flow: End-to-End Verification
- Capture: The client SDK guides the user through document capture (auto-crop, glare detection, focus quality check) and selfie capture (face positioning, initial liveness check). Captured images are compressed client-side and uploaded directly to S3 via pre-signed URLs.
- Document Analysis: The OCR service extracts text from the document (name, DOB, document number, expiry, MRZ data). The fraud detection service analyzes the image for signs of tampering (pixel manipulation, inconsistent fonts, missing security features).
- Biometric Verification: The face matching service compares the selfie against the ID photo using deep learning models (achieving 99.5%+ accuracy). The liveness service ensures the selfie is from a live person, not a photo, screen, mask, or deepfake video.
- Compliance Checks: The watchlist service screens the individual's name and DOB against OFAC, UN, EU, and PEP databases. The AML service calculates a risk score incorporating document authenticity, biometric confidence, watchlist matches, country risk, and address risk.
- Decision: The decision engine combines all verification signals to produce a final result: APPROVED, REJECTED, or ESCALATED (requires manual review). The customer is notified via webhook callback.
4. Document Verification (Passport, Driver's License, ID Card)
Document verification is the foundation of KYC — it confirms that the identity document presented is genuine, not forged, and belongs to the person presenting it. Each document type has unique characteristics, security features, and verification approaches. The platform must handle over 10,000 document types from 190+ countries, each with different layouts, languages, scripts, and security features.
Document Types & Security Features
| Document Type | Key Fields | Security Features | Verification Method |
|---|---|---|---|
| Passport | Name, DOB, nationality, photo, passport number, expiry | MRZ, hologram, RFID chip, UV features, microprint | MRZ validation + hologram analysis + chip read |
| Driver's License | Name, DOB, address, photo, license number, vehicle class | Hologram, UV overlay, microprint, guilloche patterns | Layout analysis + hologram detection + UV check |
| National ID Card | Name, DOB, address, photo, national ID number | Hologram, RFID chip, UV features, tactile elements | Chip read (if available) + visual analysis |
MRZ Validation
The Machine Readable Zone (MRZ) is present on passports and some ID cards. It encodes the holder's name, document number, nationality, date of birth, and sex in a standardized format with checksums. MRZ validation is the first and most reliable check — if the MRZ checksums fail, the document is almost certainly forged or corrupted.
C#
public class MrzValidator
{
public MrzValidationResult Validate(string mrzLine1, string mrzLine2)
{
var result = new MrzValidationResult();
// Validate document number checksum
var docNumber = mrzLine2.Substring(0, 9).TrimEnd('<');
var docChecksum = int.Parse(mrzLine2[9].ToString());
var calculatedChecksum = CalculateMrzChecksum(mrzLine2.Substring(0, 9));
result.DocumentNumberValid = docChecksum == calculatedChecksum;
// Validate date of birth checksum
var dob = mrzLine2.Substring(13, 6);
var dobChecksum = int.Parse(mrzLine2[19].ToString());
result.DateOfBirthValid = dobChecksum ==
CalculateMrzChecksum(mrzLine2.Substring(13, 6));
// Validate expiry date checksum
var expiry = mrzLine2.Substring(21, 6);
var expiryChecksum = int.Parse(mrzLine2[27].ToString());
result.ExpiryValid = expiryChecksum ==
CalculateMrzChecksum(mrzLine2.Substring(21, 6));
// Validate composite checksum
var composite = mrzLine2.Substring(0, 7) +
mrzLine2.Substring(8, 7) + mrzLine2.Substring(15, 7) +
mrzLine2.Substring(22, 7) + mrzLine2.Substring(31, 7);
var compositeChecksum = int.Parse(mrzLine2[36].ToString());
result.CompositeValid = compositeChecksum ==
CalculateMrzChecksum(composite);
result.IsValid = result.DocumentNumberValid &&
result.DateOfBirthValid && result.ExpiryValid &&
result.CompositeValid;
return result;
}
private static int CalculateMrzChecksum(string data)
{
int[] weights = { 7, 3, 1 };
int sum = 0;
for (int i = 0; i < data.Length; i++)
{
int value = char.IsDigit(data[i])
? data[i] - '0'
: GetLetterValue(data[i]);
sum += value * weights[i % 3];
}
return sum % 10;
}
private static int GetLetterValue(char c) => c switch
{
'<' => 0,
_ when c >= 'A' && c <= 'Z' => c - 'A' + 10,
_ => 0
};
}
Document Authenticity Analysis
Beyond MRZ validation, the platform performs multi-layered document analysis to detect forgery. The analysis pipeline examines four dimensions: visual consistency (fonts, alignment, layout), security features (holograms, UV patterns, microprint), material properties (thickness, reflectivity, edge profile), and digital forensics (image manipulation, JPEG artifacts, inconsistent lighting).
Each document type has a template that defines the expected layout — where the photo should be, where text fields are positioned, where security features are located. The platform maintains a database of over 10,000 document templates covering 190+ countries. Template matching compares the captured document against the expected template to detect anomalies like moved text fields, replaced photos, or missing security features.
5. OCR & Data Extraction Pipeline
Optical Character Recognition transforms the captured document image into structured data that the verification engine can process. The OCR pipeline must handle diverse document types, languages (Latin, Cyrillic, CJK, Arabic, Devanagari scripts), fonts, orientations, and image quality conditions (glare, shadow, blur, partial occlusion).
OCR Pipeline Architecture
Image Preprocessing
Raw images from smartphone cameras are rarely optimal for OCR. The preprocessing stage normalizes the image: grayscale conversion, contrast enhancement (CLAHE), noise reduction (bilateral filtering), binarization (adaptive thresholding), and deskewing (correcting rotation). For documents with glare, the system detects the glare region and attempts to recover the underlying text using multi-exposure fusion.
C#
public class DocumentImagePreprocessor
{
public PreprocessedImage Preprocess(byte[] rawImage)
{
using var image = Image.Load<Rgba32>(rawImage);
// Step 1: Detect document region and crop
var documentRegion = DetectDocumentRegion(image);
var cropped = image.Clone(x =>
x.Crop(documentRegion.BoundingRect));
// Step 2: Correct perspective distortion
var corrected = PerspectiveCorrect(cropped,
documentRegion.Corners);
// Step 3: Normalize orientation
var orientation = DetectOrientation(corrected);
if (orientation != Orientation.Normal)
corrected = RotateImage(corrected, orientation);
// Step 4: Enhance for OCR
var enhanced = EnhanceForOcr(corrected);
// Step 5: Detect and handle glare
var glareRegions = DetectGlare(enhanced);
if (glareRegions.Any())
enhanced = ReduceGlare(enhanced, glareRegions);
return new PreprocessedImage
{
Image = enhanced,
QualityScore = CalculateQualityScore(enhanced),
GlareDetected = glareRegions.Any()
};
}
}
Field Extraction & Standardization
After OCR produces raw text for each zone, the field extractor maps the text to a standardized schema. Different countries use different field names, label formats, and data structures. For example, "Date of Birth" might appear as "DOB", "Date de naissance", "Geburtsdatum", or "出生日期". The field extractor uses zone position, label matching (fuzzy string matching), and ML classification to map fields regardless of language.
6. Liveness Detection (3D Depth, Challenge-Response)
Liveness detection is the critical defense against spoofing attacks where an attacker presents a photo, video, mask, or deepfake instead of their real face. The three main approaches are passive liveness (no user interaction required), active liveness (user performs challenges), and 3D depth analysis (using structured light or time-of-flight sensors).
Attack Vectors & Countermeasures
| Attack Type | Description | Detection Method | Accuracy |
|---|---|---|---|
| Photo Attack | User holds up a printed photo or displays on another screen | Texture analysis, Moiré pattern detection, depth check | 99.5% |
| Video Replay | User plays a video of the target person | Temporal consistency, blink detection, screen artifact detection | 99.2% |
| 3D Mask | User wears a realistic silicone or 3D-printed mask | 3D depth analysis, IR reflection, skin texture analysis | 98.8% |
| Deepfake | AI-generated video of the target person | GAN artifact detection, temporal consistency, eye tracking | 98.5% |
| Digital Overlay | Digital face overlaid on captured video | Edge consistency, lighting analysis, depth mismatch | 99.0% |
Passive Liveness Detection
Passive liveness detection analyzes the selfie image or video stream without requiring the user to perform any specific action. The passive liveness model examines: texture patterns (real skin has a unique texture), Moiré patterns (screens produce interference patterns when photographed), depth cues (real faces have 3D depth that flat images lack), lighting consistency, and reflection patterns (real eyes have specular reflections that synthetic images lack).
C#
public class LivenessDetectionService
{
private readonly IFaceModelClient _faceModelClient;
private readonly IDepthEstimationService _depthService;
public async Task<LivenessResult> DetectLivenessAsync(
LivenessRequest request)
{
var result = new LivenessResult();
// Phase 1: Passive liveness (texture + Moiré + depth)
var passiveResult = await _faceModelClient
.AnalyzePassiveLivenessAsync(request.SelfieImage);
result.PassiveLivenessScore = passiveResult.Score;
result.MoireDetected = passiveResult.MoirePatternPresent;
result.DepthConsistent = passiveResult.DepthConsistent;
// Phase 2: 3D depth estimation (single-image depth prediction)
var depthMap = await _depthService
.EstimateDepthAsync(request.SelfieImage);
result.DepthVariance = CalculateDepthVariance(depthMap);
result.Is3DConsistent = result.DepthVariance > 0.15;
// Phase 3: Deepfake detection
var deepfakeResult = await _faceModelClient
.DetectDeepfakeAsync(request.SelfieImage);
result.DeepfakeScore = deepfakeResult.Score;
result.GanArtifactsDetected = deepfakeResult.GanArtifacts;
// Phase 4: Temporal analysis (if video provided)
if (request.SelfieVideo != null)
{
var temporalResult = await AnalyzeTemporalConsistency(
request.SelfieVideo);
result.BlinkDetected = temporalResult.BlinkCount > 0;
result.HeadMovementDetected = temporalResult.HeadMovement;
result.TemporalConsistency = temporalResult.Consistency;
}
// Combine scores with weighted average
result.LivenessScore = result.PassiveLivenessScore * 0.35f +
(result.Is3DConsistent ? 1.0f : 0.2f) * 0.25f +
(1.0f - result.DeepfakeScore) * 0.25f +
result.TemporalConsistency * 0.15f;
result.IsLive = result.LivenessScore > 0.85f;
return result;
}
}
Active Liveness (Challenge-Response)
Active liveness requires the user to perform specific actions that are difficult to fake. Common challenges include: "Smile", "Turn head left/right", "Blink twice", and "Read the numbers shown". The challenge-response flow must be completed within 10 seconds and should use at least 3 randomly selected challenges to prevent pre-recorded replay attacks.
3D Depth Analysis
Newer smartphones with TrueDepth (iPhone) or structured light sensors can provide real 3D depth maps. When available, the platform uses this hardware depth data for the most reliable liveness detection. For devices without depth sensors, ML-based monocular depth estimation predicts 3D depth from a single 2D image.
| Liveness Method | Accuracy | User Experience | Device Requirements |
|---|---|---|---|
| Passive (no interaction) | 97-99% | Best (no extra steps) | Standard camera |
| Active (challenge-response) | 99-99.5% | Good (1-2 extra steps) | Standard camera |
| 3D Depth (hardware) | 99.5-99.9% | Best (no extra steps) | TrueDepth / LiDAR sensor |
| 3D Depth (ML-estimated) | 96-98% | Best (no extra steps) | Standard camera |
7. Face Matching — Selfie vs. ID Photo
Face matching compares the user's live selfie against the photograph on their identity document to confirm they are the same person. The face matching model must handle significant variations: different lighting, different angles, aging (ID photos may be years old), makeup changes, facial hair changes, and glasses differences. Modern face recognition models achieve 99.5%+ accuracy on benchmark datasets like LFW and MegaFace.
Face Matching Pipeline
C#
public class FaceMatchingService
{
private readonly IFaceDetectionModel _faceDetector;
private readonly IFaceRecognitionModel _faceRecognizer;
private const float MATCH_THRESHOLD = 0.65f;
private const float HIGH_CONFIDENCE_THRESHOLD = 0.80f;
public async Task<FaceMatchResult> MatchFacesAsync(
byte[] idPhoto, byte[] selfie)
{
// Step 1: Detect faces in both images
var idFace = await _faceDetector.DetectLargestFaceAsync(idPhoto);
var selfieFace = await _faceDetector.DetectLargestFaceAsync(selfie);
if (idFace == null || selfieFace == null)
return new FaceMatchResult
{
IsMatch = false,
Error = "No face detected in one or both images"
};
// Step 2: Extract feature embeddings
var idEmbedding = await _faceRecognizer
.ExtractEmbeddingAsync(idPhoto, idFace);
var selfieEmbedding = await _faceRecognizer
.ExtractEmbeddingAsync(selfie, selfieFace);
// Step 3: Compute cosine similarity
var similarity = CosineSimilarity(
idEmbedding.Vector, selfieEmbedding.Vector);
// Step 4: Compare against thresholds
return new FaceMatchResult
{
SimilarityScore = similarity,
IsMatch = similarity >= MATCH_THRESHOLD,
IsHighConfidence = similarity >= HIGH_CONFIDENCE_THRESHOLD,
Decision = similarity switch
{
>= HIGH_CONFIDENCE_THRESHOLD => MatchDecision.HighConfidenceMatch,
>= MATCH_THRESHOLD => MatchDecision.Match,
>= 0.50f => MatchDecision.LowConfidence,
_ => MatchDecision.NoMatch
}
};
}
private static float CosineSimilarity(float[] a, float[] b)
{
float dotProduct = 0, normA = 0, normB = 0;
for (int i = 0; i < a.Length; i++)
{
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (MathF.Sqrt(normA) * MathF.Sqrt(normB));
}
}
Handling Edge Cases
| Edge Case | Challenge | Mitigation |
|---|---|---|
| Aged ID photo | Photo taken 5+ years ago | Lower threshold for older documents, escalate to manual review |
| Heavy makeup | Selfie has different appearance | ML model trained on makeup variations, focus on bone structure |
| Glasses difference | Glasses in ID but not selfie | Exclude eye region from matching, focus on other features |
| Poor ID photo quality | Blurred or low-resolution ID photo | Reject low-quality captures, request re-capture |
| Twin/similar faces | Low confidence distinguishing | Higher threshold, escalate with additional checks |
8. Document Fraud Detection (Tampering, Deepfakes)
Document fraud detection actively looks for signs that the document image has been tampered with. Fraudsters use Photoshop editing, printed documents, screen replays, and composite documents (combining elements from multiple genuine documents). The platform must detect each attack type with high accuracy.
Fraud Detection Techniques
| Fraud Type | Detection Method | Signals |
|---|---|---|
| Photo substitution | Edge analysis, lighting consistency, JPEG compression analysis | Inconsistent JPEG quality, mismatched lighting, edge artifacts |
| Text editing | Font consistency analysis, alignment checks, pixel-level forensics | Font mismatch, misalignment, inconsistent JPEG artifacts |
| Printed document | Moiré pattern detection, paper texture analysis | Printer dot patterns, paper texture, reduced color gamut |
| Screen capture | Screen reflection detection, pixel grid analysis | Screen glare, subpixel patterns, uneven brightness |
| Composite document | Consistency analysis across regions | Mixed JPEG compression, inconsistent fonts, lighting mismatches |
JPEG Forensics
JPEG compression leaves unique artifacts that reveal image manipulation. When a region is edited and re-saved, it acquires different compression artifacts than unedited regions. The JPEG forensics module analyzes the Double Quantization error pattern across the image to detect spliced regions. Error Level Analysis highlights regions with different compression levels, which often correspond to edited areas.
C#
public class DocumentFraudDetector
{
private readonly IFraudDetectionModel _mlModel;
private readonly IJpegForensics _jpegForensics;
private readonly ISecurityFeatureAnalyzer _securityFeatures;
public async Task<FraudDetectionResult> AnalyzeAsync(
DocumentImage document)
{
var result = new FraudDetectionResult();
// Layer 1: JPEG forensics
var jpegAnalysis = await _jpegForensics.AnalyzeAsync(document);
result.JpegConsistent = jpegAnalysis.IsConsistent;
result.EditedRegions = jpegAnalysis.SuspiciousRegions;
// Layer 2: ML-based fraud detection
var mlResult = await _mlModel.DetectFraudAsync(document);
result.FraudProbability = mlResult.Probability;
result.FraudType = mlResult.PredictedFraudType;
// Layer 3: Security feature verification
var securityResult = await _securityFeatures.VerifyAsync(document);
result.HologramPresent = securityResult.HologramDetected;
result.UVFeaturesPresent = securityResult.UVDetected;
result.SecurityFeatureScore = securityResult.OverallScore;
// Layer 4: Consistency analysis
result.ConsistencyScore = await AnalyzeConsistency(document);
// Composite fraud score
result.OverallFraudScore = CalculateFraudScore(
result.JpegConsistent,
result.FraudProbability,
result.SecurityFeatureScore,
result.ConsistencyScore);
result.IsFraudulent = result.OverallFraudScore > 0.7f;
result.RequiresManualReview =
result.OverallFraudScore > 0.4f &&
result.OverallFraudScore <= 0.7f;
return result;
}
}
9. Watchlist Screening (PEP, Sanctions)
Watchlist screening checks whether the individual appears on government sanctions lists, is a Politically Exposed Person (PEP), or has adverse media coverage linking them to financial crime, terrorism, or other illicit activity. This is a legal requirement under AML regulations — financial institutions must not onboard sanctioned individuals and must apply enhanced due diligence to PEPs.
Watchlist Data Sources
| Source | Coverage | Update Frequency | Type |
|---|---|---|---|
| OFAC SDN List (US) | Global, 12,000+ entries | Multiple times per week | Sanctions |
| UN Security Council | Global, 1,500+ entries | As decisions are made | Sanctions |
| EU Consolidated List | European, 10,000+ entries | Weekly | Sanctions |
| UK HMT Sanctions | UK-specific, 5,000+ entries | Weekly | Sanctions |
| PEP Database | Global, 500,000+ PEPs | Daily | Politically Exposed Persons |
| Adverse Media | Global news sources | Daily | Reputation risk |
Fuzzy Name Matching
Name matching across watchlists is deceptively complex. Names are transliterated differently (Muhammad/Mohammed/Mohamed), have spelling variations (O'Brien/Obrien), may be in different scripts (Cyrillic, Arabic, Chinese), and may include titles or suffixes. The matching engine uses: exact matching, phonetic matching (Soundex, Metaphone), fuzzy string matching (Levenshtein distance, Jaro-Winkler similarity), transliteration matching, and ML-based matching.
C#
public class WatchlistScreeningService
{
private readonly IWatchlistDatabase _watchlistDb;
private readonly INameMatchingEngine _nameMatcher;
private readonly IPhoneticEncoder _phoneticEncoder;
public async Task<WatchlistResult> ScreenAsync(
ScreeningRequest request)
{
var result = new WatchlistResult();
var normalizedName = NormalizeName(request.FullName);
// Pass 1: Exact match (fast path)
var exactMatches = await _watchlistDb
.FindExactMatchesAsync(normalizedName);
if (exactMatches.Any())
{
result.ExactMatches = exactMatches;
result.RequiresManualReview = true;
}
// Pass 2: Fuzzy + phonetic matching
var candidates = await _watchlistDb
.GetCandidatesByCountryAsync(request.CountryCode);
foreach (var candidate in candidates)
{
var nameScore = _nameMatcher.CalculateSimilarity(
normalizedName, candidate.NormalizedName);
var phoneticMatch = _phoneticEncoder.Encode(normalizedName) ==
_phoneticEncoder.Encode(candidate.NormalizedName);
if (nameScore > 0.75f || phoneticMatch)
{
var match = new WatchlistMatch
{
WatchlistEntry = candidate,
NameSimilarityScore = nameScore,
PhoneticMatch = phoneticMatch,
DOBMatch = request.DateOfBirth == candidate.DateOfBirth,
OverallScore = CalculateMatchScore(
nameScore, phoneticMatch,
request.DateOfBirth == candidate.DateOfBirth,
request.Nationality == candidate.Nationality)
};
if (match.OverallScore > 0.6f)
result.PotentialMatches.Add(match);
}
}
result.IsSanctioned = result.ExactMatches
.Any(m => m.ListType == WatchlistType.Sanctions);
result.IsPEP = result.PotentialMatches
.Any(m => m.WatchlistEntry.Type == WatchlistType.PEP &&
m.OverallScore > 0.8f);
result.HasAdverseMedia = await CheckAdverseMediaAsync(
request.FullName);
result.ScreeningDecision = DetermineDecision(result);
return result;
}
}
10. AML Risk Scoring
AML risk scoring assigns a composite risk score to each verification attempt, combining signals from all verification checks to determine the overall risk level of onboarding this individual. The risk score drives the verification decision: low-risk individuals are auto-approved, medium-risk are escalated to manual review, and high-risk are rejected or subject to enhanced due diligence. The model must be transparent and auditable — regulators require the ability to understand why a particular individual was classified as high or low risk.
Risk Scoring Model
C#
public class AmlRiskScoringService
{
private readonly RiskWeights _weights;
public RiskScore CalculateRiskScore(VerificationSignals signals)
{
var scores = new Dictionary<string, float>
{
["document"] = 1.0f - signals.DocumentAuthenticityScore,
["biometric"] = 1.0f - signals.FaceMatchScore,
["watchlist"] = signals.WatchlistRiskScore,
["country"] = GetCountryRiskScore(signals.Nationality),
["address"] = GetAddressRiskScore(signals.Address),
["document_age"] = GetDocumentAgeRisk(
signals.DocumentExpiryDate),
["device"] = signals.DeviceRiskScore,
["behavioral"] = signals.BehavioralRiskScore
};
float compositeScore = 0;
foreach (var (factor, score) in scores)
compositeScore += score * _weights.GetWeight(factor);
compositeScore = Math.Clamp(compositeScore, 0f, 100f);
return new RiskScore
{
CompositeScore = compositeScore,
FactorScores = scores,
RiskLevel = compositeScore switch
{
<= 30 => RiskLevel.Low,
<= 70 => RiskLevel.Medium,
_ => RiskLevel.High
},
RecommendedAction = compositeScore switch
{
<= 30 => Action.AutoApprove,
<= 70 => Action.ManualReview,
_ => Action.RejectOrEDD
}
};
}
private float GetCountryRiskScore(string nationality)
{
var highRisk = new HashSet<string> { "KP", "IR", "MM" };
var greyList = new HashSet<string>
{ "PK", "LB", "UG", "TZ", "MU" };
if (highRisk.Contains(nationality)) return 0.9f;
if (greyList.Contains(nationality)) return 0.6f;
return 0.2f;
}
}
Risk Factor Weights
| Factor | Weight | Rationale |
|---|---|---|
| Document authenticity | 25% | Forged documents are the strongest indicator of fraud |
| Biometric match | 20% | Non-match indicates the person is not the document holder |
| Watchlist match | 20% | Sanctions matches are legally required blockers |
| Country risk | 15% | FATF grey/black list countries have higher AML risk |
| Address risk | 10% | High-risk jurisdictions increase risk |
| Device/behavioral | 10% | Anomalous behavior indicates potential fraud |
11. Address Verification
Address verification confirms that the address provided matches a real, verifiable address and, where required, matches the address on the identity document. The platform supports three verification methods: database verification (checking against utility company or government databases), document-based verification (extracting the address from the ID document), and third-party verification (using services like USPS address validation or Google Maps Geocoding API).
Address Verification Methods
| Method | Coverage | Accuracy | Latency |
|---|---|---|---|
| Utility database check | 60% of addresses (US, UK, EU) | 99% | 1-3 seconds |
| Government database check | 30% (varies by country) | 99.5% | 2-10 seconds |
| USPS/courier validation | US addresses only | 99.9% | < 1 second |
| Document-based extraction | All countries (if on ID) | OCR-dependent (95%+) | < 1 second |
| Geocoding validation | Global | 85% | < 1 second |
The address verification flow first normalizes the address using the Address Data Council standards, then attempts database verification where available. If no database is available, the system falls back to geocoding and document-based extraction. The verification result includes a confidence score and the verification method used, which the risk scoring model factors into its calculation.
12. Phone & Email Verification
Phone and email verification serve dual purposes: confirming the user's contact channels are legitimate (not disposable or fraudulent), and providing a channel for ongoing communication. Phone verification uses SMS or voice OTP. Email verification uses a confirmation link or OTP. Both channels are checked against risk databases — disposable email providers, VoIP phone numbers, and known fraud-associated numbers are flagged.
Phone Verification Flow
C#
public class PhoneVerificationService
{
private readonly ISmsProvider _smsProvider;
private readonly IPhoneIntelligence _phoneIntelligence;
private readonly IVerificationStore _store;
public async Task<PhoneVerificationResult> VerifyAsync(
string phoneNumber, string countryCode)
{
var result = new PhoneVerificationResult();
if (!PhoneNumberValidator.IsValid(phoneNumber, countryCode))
{
result.Status = PhoneVerificationStatus.Invalid;
return result;
}
var intel = await _phoneIntelligence.AnalyzeAsync(phoneNumber);
result.IsDisposable = intel.IsDisposable;
result.IsVoip = intel.IsVoip;
if (intel.IsDisposable)
{
result.Status = PhoneVerificationStatus.Rejected;
result.Reason = "Disposable phone number not accepted";
return result;
}
var otp = GenerateSecureOtp(6);
var otpExpiry = DateTime.UtcNow.AddMinutes(5);
await _store.StoreOtpAsync(phoneNumber, otp, otpExpiry);
await _smsProvider.SendAsync(phoneNumber,
$"Your verification code is: {otp}. Valid for 5 minutes.");
result.Status = PhoneVerificationStatus.OtpSent;
result.AttemptsRemaining = 3;
return result;
}
public async Task<PhoneVerificationResult> ConfirmOtpAsync(
string phoneNumber, string code)
{
var stored = await _store.GetOtpAsync(phoneNumber);
if (stored == null || stored.Expiry < DateTime.UtcNow)
return new PhoneVerificationResult
{ Status = PhoneVerificationStatus.Expired };
if (stored.Attempts >= 3)
return new PhoneVerificationResult
{ Status = PhoneVerificationStatus.Locked };
if (stored.Code != code)
{
stored.Attempts++;
await _store.UpdateOtpAsync(stored);
return new PhoneVerificationResult
{
Status = PhoneVerificationStatus.Incorrect,
AttemptsRemaining = 3 - stored.Attempts
};
}
await _store.MarkVerifiedAsync(phoneNumber);
return new PhoneVerificationResult
{
Status = PhoneVerificationStatus.Verified,
VerifiedAt = DateTime.UtcNow
};
}
}
Email Risk Checks
Before sending a verification email, the system performs risk checks: domain age (newly registered domains are suspicious), disposable email detection (checking against 10,000+ known providers), free email provider detection (Gmail, Yahoo flagged for higher-risk verifications), role-based email detection (admin@, info@ flagged), and SMTP RCPT TO verification (confirming the address accepts mail without sending actual mail).
13. Database Verification (Government Databases)
Database verification cross-references the extracted document data against authoritative government or institutional databases to confirm the document is genuine and hasn't been reported lost, stolen, or cancelled. The platform supports three verification tiers: full verification (real-time API check), limited verification (batch check with 24-hour turnaround), and document-only (no database available).
Database Verification by Country
| Country | Available Databases | Verification Type | Latency |
|---|---|---|---|
| United States | SSA, DMV, USPS | Real-time API | 1-3 seconds |
| United Kingdom | DVLA, Royal Mail | Real-time API | 1-5 seconds |
| Germany | Meldebescheinigung | Limited (batch) | 24 hours |
| India | Aadhaar (UIDAI) | Real-time API (with consent) | 2-5 seconds |
| Brazil | RG, CPF | Limited (batch) | 1-3 days |
| Nigeria | NIMC (NIN) | Limited (batch) | 1-5 days |
| Japan | My Number Card | Real-time API | 1-3 seconds |
| Australia | Document Verification Service | Real-time API | 1-2 seconds |
When a database check returns a positive match, the verification confidence increases significantly — the combination of document analysis + biometric matching + database verification produces a near-certain verification. When no database is available, the platform relies on other verification signals and may apply additional checks to compensate.
14. KYC Workflow — Collection to Approval
The KYC workflow is the state machine that governs the entire verification process from initial document submission to final approval or rejection. It must handle the full lifecycle: collecting documents, running automated checks, routing to manual review when needed, handling re-submissions, and maintaining an audit trail of every state transition.
C#
public class KycWorkflowEngine
{
private readonly IVerificationStore _store;
private readonly IVerificationOrchestrator _orchestrator;
private readonly IWebhookDispatcher _webhooks;
public async Task<KycResult> ProcessVerificationAsync(
KycVerificationRequest request)
{
var verification = new Verification
{
Id = Guid.NewGuid(),
ClientId = request.ClientId,
UserId = request.UserId,
Status = VerificationStatus.DocumentCollection,
CreatedAt = DateTime.UtcNow
};
await _store.SaveAsync(verification);
// Step 1: Document Analysis
verification.Status = VerificationStatus.DocumentAnalysis;
var docResult = await _orchestrator
.AnalyzeDocumentAsync(request.DocumentImages);
verification.DocumentResult = docResult;
await UpdateAndNotify(verification);
// Step 2: Selfie & Biometric Verification
verification.Status = VerificationStatus.BiometricVerification;
var biometricResult = await _orchestrator
.VerifyBiometricAsync(request.SelfieImage,
docResult.ExtractedPhoto);
verification.BiometricResult = biometricResult;
await UpdateAndNotify(verification);
// Step 3: Watchlist Screening
verification.Status = VerificationStatus.WatchlistScreening;
var watchlistResult = await _orchestrator
.ScreenWatchlistAsync(docResult.ExtractedData);
verification.WatchlistResult = watchlistResult;
await UpdateAndNotify(verification);
// Step 4: AML Risk Scoring
verification.Status = VerificationStatus.RiskScoring;
var riskScore = await _orchestrator
.CalculateRiskScoreAsync(verification);
verification.RiskScore = riskScore;
await UpdateAndNotify(verification);
// Step 5: Decision
verification.Status = riskScore.RiskLevel switch
{
RiskLevel.Low => VerificationStatus.Approved,
RiskLevel.High => VerificationStatus.Rejected,
RiskLevel.Medium => VerificationStatus.ManualReview
};
verification.CompletedAt = DateTime.UtcNow;
await _store.SaveAsync(verification);
await _webhooks.SendAsync(request.CallbackUrl, new WebhookPayload
{
VerificationId = verification.Id,
Status = verification.Status,
RiskScore = riskScore.CompositeScore,
Timestamp = verification.CompletedAt
});
return MapToResult(verification);
}
}
Workflow States & Transitions
| State | Description | Next States | Trigger |
|---|---|---|---|
| DocumentCollection | Waiting for user to upload documents | DocumentAnalysis | Document uploaded |
| DocumentAnalysis | OCR + authenticity checks running | SelfieCollection | Analysis complete |
| SelfieCollection | Waiting for user to capture selfie | BiometricVerification | Selfie captured |
| BiometricVerification | Face matching + liveness running | WatchlistScreening | Biometric check complete |
| WatchlistScreening | Checking sanctions/PEP databases | AmlRiskScoring | Screening complete |
| AmlRiskScoring | Calculating composite risk score | Approved/Rejected/ManualReview | Risk score determined |
| ManualReview | Analyst reviewing evidence | Approved/Rejected/ResubmitRequest | Analyst decision |
| Approved | Verification passed | Terminal | — |
| Rejected | Verification failed | Terminal | — |
15. Tiered Verification Levels
Not all use cases require the same level of verification. The platform supports configurable verification tiers that clients can select based on their regulatory requirements and risk appetite.
Verification Tiers
| Tier | Checks Included | Typical Use Case | Completion Time |
|---|---|---|---|
| Tier 1 — Basic | Document verification + selfie match | Freelance marketplaces, social media age verification | 15-20 seconds |
| Tier 2 — Standard | Tier 1 + liveness + watchlist screening | Fintech, neobanks, insurance | 20-30 seconds |
| Tier 3 — Enhanced | Tier 2 + AML risk scoring + address verification | Cryptocurrency exchanges, trading platforms | 25-35 seconds |
| Tier 4 — Full | Tier 3 + database + phone/email verification | Banks, lending, regulated financial services | 30-60 seconds |
C#
public class VerificationTierConfigurator
{
public VerificationPipeline GetPipeline(VerificationTier tier)
{
return tier switch
{
VerificationTier.Basic => new VerificationPipeline
{
Steps = new[]
{
VerificationStep.DocumentAnalysis,
VerificationStep.FaceMatching
},
AutoApproveThreshold = 0.70f,
AutoRejectThreshold = 0.30f
},
VerificationTier.Standard => new VerificationPipeline
{
Steps = new[]
{
VerificationStep.DocumentAnalysis,
VerificationStep.FaceMatching,
VerificationStep.LivenessDetection,
VerificationStep.WatchlistScreening
},
AutoApproveThreshold = 0.75f,
AutoRejectThreshold = 0.25f
},
VerificationTier.Enhanced => new VerificationPipeline
{
Steps = new[]
{
VerificationStep.DocumentAnalysis,
VerificationStep.FaceMatching,
VerificationStep.LivenessDetection,
VerificationStep.WatchlistScreening,
VerificationStep.AmlRiskScoring,
VerificationStep.AddressVerification
},
AutoApproveThreshold = 0.80f,
AutoRejectThreshold = 0.20f
},
VerificationTier.Full => new VerificationPipeline
{
Steps = new[]
{
VerificationStep.DocumentAnalysis,
VerificationStep.FaceMatching,
VerificationStep.LivenessDetection,
VerificationStep.WatchlistScreening,
VerificationStep.AmlRiskScoring,
VerificationStep.AddressVerification,
VerificationStep.PhoneVerification,
VerificationStep.EmailVerification,
VerificationStep.DatabaseVerification
},
AutoApproveThreshold = 0.85f,
AutoRejectThreshold = 0.15f
}
};
}
}
Clients can create custom tiers by selecting specific verification steps and configuring their own thresholds. The platform provides a configuration API that allows real-time tier setting updates with versioning and rollback capability.
16. Re-verification & Ongoing Monitoring
KYC is not a one-time event — it is an ongoing process. Customers must be re-verified periodically (typically annually for standard risk, quarterly for high risk), when their risk profile changes, or when their identity documents expire.
Re-verification Triggers
| Trigger | Frequency | Action | Customer Impact |
|---|---|---|---|
| Document expiry | When expiry date reached | Request new document | Account restricted until provided |
| Periodic re-verification | 12 months (low), 6 months (medium), 3 months (high) | Full re-verification flow | 30-day grace period |
| New watchlist match | Real-time (daily batch) | Immediate enhanced review | Account may be frozen |
| Adverse media match | Daily scan | Enhanced due diligence | Account may be restricted |
| Risk score change | Continuous | Re-evaluate risk tier | May trigger additional checks |
C#
public class ReverificationScheduler
{
private readonly IVerificationStore _store;
public async Task CheckReverificationTriggersAsync()
{
var expiredDocs = await _store
.GetVerificationsWithExpiredDocumentsAsync();
foreach (var v in expiredDocs)
await TriggerReverificationAsync(v,
ReverificationTrigger.DocumentExpiry);
var dueForPeriodic = await _store
.GetVerificationsDueForPeriodicReviewAsync();
foreach (var v in dueForPeriodic)
{
var interval = GetReverificationInterval(v.RiskLevel);
if (DateTime.UtcNow - v.LastVerifiedAt >= interval)
await TriggerReverificationAsync(v,
ReverificationTrigger.Periodic);
}
}
private TimeSpan GetReverificationInterval(RiskLevel level)
{
return level switch
{
RiskLevel.Low => TimeSpan.FromDays(365),
RiskLevel.Medium => TimeSpan.FromDays(180),
RiskLevel.High => TimeSpan.FromDays(90),
_ => TimeSpan.FromDays(365)
};
}
}
17. Embedded Verification SDK & Webhook Callbacks
The verification SDK is the primary integration point for clients. It provides embeddable JavaScript components for web and native SDKs for iOS and Android. The SDK handles the entire capture flow with real-time quality guidance, uploads images directly to encrypted storage, and manages the verification lifecycle.
SDK Integration Example
C#
// Server-side: Create a verification session and get a client token
[HttpPost("api/verification/start")]
public async Task<ActionResult> StartVerification(
[FromBody] VerificationRequest request)
{
var session = await _kycService.CreateSessionAsync(
new SessionRequest
{
ClientId = _authContext.ClientId,
UserId = request.UserId,
Tier = VerificationTier.Standard,
CallbackUrl = request.CallbackUrl,
Metadata = request.Metadata
});
return Ok(new
{
SessionId = session.Id,
ClientToken = session.ClientToken,
Expiry = session.Expiry
});
}
// Client-side: Initialize SDK (JavaScript)
const verification = await KYC.initialize({
clientToken: "eyJhbGciOi...",
container: "#kyc-container",
steps: ["document", "selfie"],
document: {
types: ["passport", "driver_license", "national_id"],
country: "US"
},
selfie: {
liveness: "active",
challenges: ["smile", "blink"]
},
onComplete: (result) => {
console.log("Verification:", result.verificationId);
console.log("Status:", result.status);
},
onError: (error) => {
console.error("KYC Error:", error.code, error.message);
}
});
verification.start();
Webhook Callbacks
Webhook callbacks notify clients when verification status changes. The platform sends HTTP POST requests with HMAC-SHA256 signed JSON payloads. Webhooks are retried with exponential backoff (3 retries over 30 minutes). Clients can register multiple callback URLs for redundancy.
JSON
{
"event": "verification.completed",
"verification_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "approved",
"risk_score": 15.2,
"timestamp": "2025-07-15T14:30:00Z",
"data": {
"document_type": "passport",
"document_country": "US",
"face_match_score": 0.92,
"liveness_score": 0.95,
"watchlist_clear": true,
"aml_risk_level": "low"
},
"signature": "a1b2c3d4e5f6..."
}
| Webhook Event | Trigger | Use Case |
|---|---|---|
| verification.started | User begins verification flow | Track funnel conversion |
| verification.completed | Automated decision made | Grant/deny access |
| verification.pending_review | Escalated to manual review | Notify operations team |
| verification.approved | Final approval | Grant access |
| verification.rejected | Final rejection | Notify user, deny access |
| verification.resubmission_required | Analyst requests new documents | Prompt user to re-upload |
18. Multi-Country Support & Data Residency
A global KYC platform must support identity documents from 190+ countries while complying with data residency regulations that require certain data to be stored within specific geographic boundaries.
Data Residency Requirements by Region
| Region | Regulation | Requirement | Implementation |
|---|---|---|---|
| European Union | GDPR | Personal data must stay in EU | EU-hosted storage cluster |
| Brazil | LGPD | Processing consent required | Brazilian data center |
| India | DPDP Act | Critical data must be stored in India | Indian data center for Aadhaar |
| China | PIPL | Cross-border transfer requires assessment | China-hosted processing |
| Russia | Federal Law 152 | Russian citizen data in Russia | Russian data center |
| United States | CCPA/CPRA | Right to deletion, opt-out | US-hosted storage, deletion API |
The routing layer examines the user's declared nationality, IP geolocation, and client configuration to determine the appropriate data region. Document images and PII are processed and stored within the determined region. ML models are deployed to each region to avoid cross-border data transfer during inference.
19. API Design
The platform exposes a RESTful API for verification management, status queries, and administrative operations. All API calls are authenticated via API keys (server-to-server) or client tokens (browser/mobile SDK).
Verification API
HTTP
POST /api/v1/verifications # Create verification session
GET /api/v1/verifications/{id} # Get verification details
GET /api/v1/verifications/{id}/status # Get current status (lightweight)
GET /api/v1/verifications/{id}/documents # Get extracted document data
GET /api/v1/verifications/{id}/decision # Get decision details
POST /api/v1/verifications/{id}/resubmit # Request document resubmission
POST /api/v1/verifications/{id}/approve # Manual approval (reviewer)
POST /api/v1/verifications/{id}/reject # Manual rejection (reviewer)
GET /api/v1/verifications?status=pending_review # List verifications (filtered)
POST /api/v1/screening # Run watchlist screening
GET /api/v1/screening/{id} # Get screening results
GET /api/v1/config/tiers # Get verification tier configs
PUT /api/v1/config/tiers/{tier} # Update tier configuration
GET /api/v1/audit/verifications/{id} # Get full audit trail
Create Verification Request
JSON
{
"user_id": "user_abc123",
"type": "kyc",
"tier": "standard",
"documents": [
{
"type": "passport",
"country": "US",
"images": {
"front": "https://upload.example.com/doc_front.jpg?token=...",
"back": null
}
}
],
"selfie": {
"type": "live_capture",
"liveness": "active"
},
"callback_url": "https://api.mysite.com/webhooks/kyc",
"redirect_url": "https://mysite.com/kyc/complete",
"metadata": {
"account_id": "acc_12345",
"flow": "onboarding"
},
"options": {
"manual_review": true,
"data_deletion_after_days": 365
}
}
API Error Handling
| Status Code | Error Type | Description |
|---|---|---|
| 400 | invalid_request | Missing required fields or invalid format |
| 401 | unauthorized | Invalid or missing API key |
| 403 | forbidden | API key lacks permission |
| 404 | not_found | Verification doesn't exist |
| 409 | conflict | Verification already exists for this user |
| 429 | rate_limited | Too many requests, retry after Retry-After |
| 500 | internal_error | Server error, contact support |
Idempotency-Key header. If provided, the server returns the same response for duplicate requests within a 24-hour window. This prevents duplicate verifications from network retries.
20. Security & Biometric Data Protection
Biometric data (face images, facial embeddings, fingerprints) is among the most sensitive categories of personal data. Unlike a password, biometric data cannot be changed if compromised. The platform implements defense-in-depth security across every layer.
Security Architecture
| Layer | Protection | Implementation |
|---|---|---|
| Data at rest | AES-256 encryption | AWS KMS / Azure Key Vault, encrypted S3 and RDS |
| Data in transit | TLS 1.3 | Certificate pinning in SDKs, HSTS, min TLS enforcement |
| Access control | RBAC + MFA | Analyst roles (reviewer, admin, auditor), MFA required |
| Audit logging | Immutable audit trail | S3 Object Lock, append-only log store, 7-year retention |
| Data minimization | Delete when unnecessary | Configurable retention policies, auto PII deletion |
| Pseudonymization | Token-based references | Internal systems use verification tokens, not raw PII |
| Network security | VPC isolation | Private subnets for ML services, WAF, DDoS protection |
| Secret management | Centralized vault | HashiCorp Vault / AWS Secrets Manager, dynamic secrets |
C#
public class BiometricDataProtector
{
private readonly IKeyVault _keyVault;
private readonly IEncryptionService _encryption;
public async Task<EncryptedBiometricData> EncryptBiometricAsync(
BiometricData data, Guid userId)
{
// Generate a unique data encryption key (DEK) for this user
var dek = await _keyVault.GenerateDataKeyAsync(
keySize: 256,
context: $"biometric:{userId}");
// Encrypt the facial embedding
var encryptedEmbedding = _encryption.EncryptAes256(
data.FacialEmbedding, dek.Plaintext);
// Encrypt the selfie image
var encryptedSelfie = _encryption.EncryptAes256(
data.SelfieImage, dek.Plaintext);
// Encrypt the DEK with the master key (envelope encryption)
var encryptedDek = _encryption.EncryptRsa(
dek.Plaintext,
await _keyVault.GetMasterKeyAsync("biometric"));
// Securely erase the plaintext DEK from memory
dek.ZeroPlaintext();
return new EncryptedBiometricData
{
UserId = userId,
EncryptedEmbedding = encryptedEmbedding,
EncryptedSelfie = encryptedSelfie,
EncryptedDek = encryptedDek,
Algorithm = "AES-256-GCM",
KeyVersion = await _keyVault.GetCurrentKeyVersionAsync(),
CreatedAt = DateTime.UtcNow
};
}
}
Biometric Data Lifecycle
21. Compliance (KYC/AML, GDPR, CCPA)
Compliance is the foundation of the platform. Every design decision must consider regulatory requirements. The three primary frameworks are KYC/AML regulations (financial crime prevention), GDPR (European data protection), and CCPA/CPRA (California consumer privacy).
Regulatory Requirements Matrix
| Requirement | KYC/AML | GDPR | CCPA/CPRA |
|---|---|---|---|
| Data collection | Mandatory for financial services | Must have lawful basis | Must disclose categories |
| Data processing | Required for compliance | Must be necessary and proportionate | Must disclose purposes |
| Data retention | Minimum 5 years | No longer than necessary | Retain only as disclosed |
| Data deletion | Cannot delete if under investigation | Right to erasure (72-hour SLA) | Right to delete (45-day SLA) |
| Data portability | Not required | Right to data portability | Right to data portability |
| Cross-border transfer | Varies by jurisdiction | Requires adequacy decision or SCCs | Not specifically restricted |
| Breach notification | Report to FIU within 24 hours | Authority: 72 hours | Consumers: most expedient time |
C#
public class GdprComplianceService
{
private readonly IVerificationStore _store;
private readonly IBiometricStore _biometricStore;
private readonly IAuditLogger _auditLogger;
public async Task<ErasureResult> ProcessErasureRequestAsync(
ErasureRequest request)
{
var activeInvestigations = await _store
.GetActiveInvestigationsAsync(request.UserId);
if (activeInvestigations.Any())
{
await _auditLogger.LogAsync(new AuditEntry
{
Action = "erasure_denied",
UserId = request.UserId,
Reason = "Active AML investigation",
LegalBasis = "GDPR Art. 17(3)(b)",
Timestamp = DateTime.UtcNow
});
return new ErasureResult
{
Status = ErasureStatus.Denied,
Reason = "Legal obligation requires data retention"
};
}
// Delete biometric data
await _biometricStore.DeleteAllBiometricsAsync(request.UserId);
var verifications = await _store
.GetVerificationsAsync(request.UserId);
foreach (var v in verifications)
{
if (v.RetentionRequired)
await _store.AnonymizeAsync(v.Id);
else
await _store.DeleteAsync(v.Id);
}
await _auditLogger.LogAsync(new AuditEntry
{
Action = "erasure_completed",
UserId = request.UserId,
RecordsAnonymized = verifications.Count(v => v.RetentionRequired),
RecordsDeleted = verifications.Count(v => !v.RetentionRequired),
Timestamp = DateTime.UtcNow
});
return new ErasureResult { Status = ErasureStatus.Completed };
}
}
22. Monitoring & Alerting
Monitoring for a KYC platform must track both system health metrics (latency, error rates, throughput) and business metrics (approval rates, false rejection rates, fraud detection rates). A system that is technically healthy but rejecting 20% of legitimate users is just as broken as one returning HTTP 500 errors.
Key Metrics Dashboard
| Metric | Alert Threshold | Severity |
|---|---|---|
| Verification latency P99 | > 45 seconds | Warning |
| Automated approval rate | < 80% | Warning |
| False rejection rate | > 3% | Critical |
| Manual review queue depth | > 1,000 | Warning |
| Manual review SLA breach | > 5% exceed 4 hours | Critical |
| ML model accuracy | < 97% on benchmark | Critical |
| Fraud detection rate | < 95% | Critical |
| OCR accuracy | < 95% character-level | Warning |
| API error rate | > 1% | Critical |
| SDK crash rate | > 0.5% | Warning |
ML Model Monitoring
ML model performance degrades over time as fraudsters evolve their techniques. The platform continuously monitors model performance against known-good and known-fraud datasets, retrains models when performance drops below thresholds, and A/B tests new versions before full deployment.
C#
public class ModelPerformanceMonitor
{
private readonly IMetricsCollector _metrics;
private readonly IAlertService _alerts;
public async Task EvaluateModelPerformanceAsync(
string modelName, ModelPerformanceData data)
{
var accuracy = (float)(data.TruePositives + data.TrueNegatives) /
data.TotalPredictions;
var precision = (float)data.TruePositives /
(data.TruePositives + data.FalsePositives);
var recall = (float)data.TruePositives /
(data.TruePositives + data.FalseNegatives);
var f1 = 2 * (precision * recall) / (precision + recall);
_metrics.Gauge("ml.model.accuracy", accuracy,
new Tags { ["model"] = modelName });
_metrics.Gauge("ml.model.f1", f1,
new Tags { ["model"] = modelName });
if (accuracy < 0.97f)
{
await _alerts.SendAsync(new Alert
{
Severity = AlertSeverity.Critical,
Title = $"ML model accuracy below threshold",
Message = $"Model {modelName} accuracy: {accuracy:P2} " +
$"(threshold: 97%). Retraining recommended.",
Action = "Trigger model retraining pipeline"
});
}
var baseline = await GetBaselineAccuracyAsync(modelName);
var drift = baseline - accuracy;
if (drift > 0.02f)
{
await _alerts.SendAsync(new Alert
{
Severity = AlertSeverity.Warning,
Title = "Model performance drift detected",
Message = $"Model {modelName} accuracy dropped by " +
$"{drift:P2} from baseline {baseline:P2}"
});
}
}
}
23. Cost Estimation
| Component | Monthly Cost | Notes |
|---|---|---|
| API Gateway + Load Balancers | $800 | Global, with WAF |
| Verification Orchestrator (4 nodes) | $2,400 | 4x m5.xlarge |
| ML Inference (GPU instances) | $8,000 | 4x g4dn.xlarge |
| PostgreSQL (multi-region) | $4,000 | 3 regions x 3-node clusters |
| Redis (multi-region) | $1,500 | Caching + sessions |
| S3 (encrypted storage) | $2,000 | ~50TB + Glacier lifecycle |
| Kafka (event streaming) | $1,500 | Verification event pipeline |
| Watchlist data feeds | $3,000 | OFAC, UN, EU, PEP subscriptions |
| Manual Review Platform | $1,000 | Review dashboard + analyst tools |
| Monitoring & Logging | $1,200 | Prometheus, Grafana, ELK |
| Security (KMS, WAF, audit) | $800 | Key management, audit logging |
| Total | ~$26,200 |
Per-Verification Cost
| Tier | ML Compute | Storage | Third-Party APIs | Total |
|---|---|---|---|---|
| Tier 1 (Basic) | $0.03 | $0.01 | $0.00 | $0.04 |
| Tier 2 (Standard) | $0.05 | $0.01 | $0.02 | $0.08 |
| Tier 3 (Enhanced) | $0.05 | $0.01 | $0.05 | $0.11 |
| Tier 4 (Full) | $0.05 | $0.01 | $0.10 | $0.16 |
Cost Optimization Strategies
| Optimization | Savings | Impact |
|---|---|---|
| Spot instances for ML inference | ~$4,000/month | 50% of ML cost |
| S3 Intelligent-Tiering | ~$600/month | Auto-move old images |
| Reserved instances (1yr) | ~$3,000/month | 30% discount on compute |
| On-device pre-screening | ~$2,000/month | Reject bad captures early |
| Optimized Total | ~$16,600/month | 37% reduction |
24. Testing Strategy
Testing a KYC platform is uniquely challenging because you cannot easily generate "real" identity documents for test purposes, you need to test against a vast variety of document types, and you must verify that fraud detection models catch known attack vectors. The testing strategy spans four levels: unit tests, integration tests, accuracy tests, and end-to-end tests.
Unit Tests
C#
[TestClass]
public class MrzValidatorTests
{
private readonly MrzValidator _validator = new();
[TestMethod]
public void Should_Validate_Correct_US_Passport_MRZ()
{
var line1 = "P<USDOE<JOHN<<<<<<<<<<<<<<";
var line2 = "L898902C36US7004012M1501017<<<<<<00";
var result = _validator.Validate(line1, line2);
Assert.IsTrue(result.IsValid);
Assert.AreEqual("DOE", result.ExtractedData.Surname);
Assert.AreEqual("JOHN", result.ExtractedData.GivenNames);
Assert.AreEqual("US", result.ExtractedData.Nationality);
}
[TestMethod]
public void Should_Detect_Invalid_Checksum()
{
var line1 = "P<USDOE<JOHN<<<<<<<<<<<<<<";
var line2 = "L898902C36US7004012M1501017<<<<<<99";
var result = _validator.Validate(line1, line2);
Assert.IsFalse(result.IsValid);
Assert.IsFalse(result.CompositeValid);
}
}
ML Model Accuracy Tests
C#
[TestClass]
public class FaceMatchingAccuracyTests
{
private readonly FaceMatchingService _service = new();
private readonly LabeledDataset _benchmarkDataset;
[TestMethod]
public async Task Should_Achieve_99Percent_Accuracy()
{
var results = new List<FaceMatchResult>();
foreach (var pair in _benchmarkDataset.SamePersonPairs)
{
var result = await _service.MatchFacesAsync(
pair.Image1, pair.Image2);
results.Add(result);
}
foreach (var pair in _benchmarkDataset.DifferentPersonPairs)
{
var result = await _service.MatchFacesAsync(
pair.Image1, pair.Image2);
results.Add(result);
}
var truePositives = results.Count(r =>
r.IsMatch && r.ExpectedMatch);
var trueNegatives = results.Count(r =>
!r.IsMatch && !r.ExpectedMatch);
var accuracy = (float)(truePositives + trueNegatives) /
results.Count;
Assert.IsTrue(accuracy >= 0.99f,
$"Accuracy {accuracy:P2} below 99% threshold");
}
[TestMethod]
public async Task Should_Detect_Photo_Presentation_Attack()
{
foreach (var attack in _benchmarkDataset.PhotoAttackSamples)
{
var livenessResult = await _service
.DetectLivenessAsync(attack.Image);
Assert.IsTrue(!livenessResult.IsLive,
$"Failed to detect photo attack: {attack.Description}");
}
}
}
Test Coverage Targets
| Component | Test Type | Coverage Target |
|---|---|---|
| MRZ validation | Unit | 100% |
| Workflow state machine | Unit | 100% |
| Risk scoring model | Unit | 95%+ |
| OCR pipeline | Integration | 90%+ (200+ document types) |
| Face matching | Accuracy | 99%+ on LFW, MegaFace |
| Liveness detection | Accuracy | 98%+ attack detection |
| Watchlist screening | Integration | 95%+ |
| End-to-end flow | E2E | All happy paths + error paths |
| Webhook delivery | Integration | 99.9%+ delivery |
25. Interview Q&A Deep Dive
Q1: How do you handle a situation where the OCR extracts the wrong name from a document?
Answer: OCR errors in name extraction are common, especially with non-Latin scripts, worn documents, or poor capture quality. The system uses multiple extraction strategies in parallel: MRZ extraction (most reliable for passports, uses checksums), zone-based OCR (extracts text from the name zone using language-specific models), and ML field classification (training a model to recognize field types regardless of language). The results from all strategies are compared — if they agree, confidence is high. If they disagree, the system escalates to manual review with all extracted variants presented to the analyst. Additionally, the extracted name is cross-referenced against the watchlist using all variants to maximize matching coverage.
Q2: How do you prevent a deepfake from bypassing liveness detection?
Answer: The defense is multi-layered: (1) Passive liveness analysis examines texture patterns, Moiré artifacts, and depth consistency that deepfakes struggle to replicate perfectly. (2) Active liveness requires real-time challenges (smile, turn head, blink) that pre-recorded deepfakes cannot respond to dynamically. (3) 3D depth analysis (when hardware sensors are available) detects flat deepfake presentations. (4) GAN artifact detection specifically targets the statistical fingerprints left by generative adversarial networks. (5) Temporal consistency analysis checks for frame-to-frame inconsistencies that deepfakes exhibit. No single layer is sufficient — the combination of all five makes it extremely difficult for deepfakes to pass. We also continuously update our detection models with the latest deepfake samples and retrain quarterly.
Q3: How do you handle verification for a user whose document is from a country with no government database for verification?
Answer: Database verification is not available for all countries. When it's not available, the platform relies on the other verification signals: document analysis (template matching, security feature verification, JPEG forensics), biometric matching (face match + liveness), and watchlist/AML screening. The risk scoring model accounts for database availability — a verification without database check receives a slightly higher risk score than one with database confirmation. For high-risk use cases (banking, crypto), we may require additional verification steps (enhanced liveness, address verification, manual review) to compensate for the missing database check. The platform's coverage matrix shows which verification methods are available for each country, allowing clients to set appropriate tier requirements.
Q4: How do you scale the face matching service to handle millions of daily verifications?
Answer: Face matching is computationally intensive — each comparison requires running a CNN forward pass on both images and computing cosine similarity on 512-dimensional embeddings. At 1M verifications/day, that's ~12 QPS sustained, peaking at ~50 QPS. The scaling strategy: (1) Deploy face matching models on GPU instances (g4dn.xlarge or equivalent) with ONNX Runtime for optimized inference. (2) Batch multiple comparisons into single GPU inference calls when possible. (3) Cache face embeddings — if a user resubmits a selfie, reuse the cached ID photo embedding. (4) Auto-scale GPU instances based on inference queue depth. (5) For extreme scale, use model quantization (INT8) to reduce GPU memory and increase throughput by 3-4x with minimal accuracy loss.
Q5: How do you handle a user who submits a genuine document but it's not theirs (stolen identity)?
Answer: This is the core reason for biometric verification. Even if the document is genuine, the face matching step compares the live selfie against the photo on the document. If the selfie doesn't match the ID photo, the verification fails. Liveness detection ensures the selfie is from a live person (not a photo of the document holder). Together, these prevent identity theft even when genuine documents are used. Additional defenses include: behavioral analysis (typing patterns, device usage patterns), address verification (confirming the user knows the address on the ID), and phone/email verification (confirming the user controls the contact channels). For high-risk cases, manual review can examine subtle indicators that automated systems miss.
Q6: How do you handle re-verification when a customer's risk profile changes?
Answer: The re-verification engine runs daily batch scans comparing all active customers against updated watchlist data, adverse media feeds, and risk indicators. When a new match is detected, the system triggers a re-verification workflow: the customer is notified via email/SMS and given a link to complete re-verification. The re-verification flow re-runs all applicable checks (document, biometric, watchlist, AML). If the customer doesn't respond within the grace period (typically 30 days), their account is progressively restricted — first limiting new transactions, then withdrawals, then full freeze. The system tracks re-verification status per customer and produces daily compliance reports showing the re-verification pipeline health.
Q7: How do you balance false acceptance vs. false rejection?
Answer: The balance is configured per client based on their risk appetite and regulatory requirements. A cryptocurrency exchange may accept a 3% false rejection rate to achieve 0.05% false acceptance, while a gig economy platform may accept 0.5% false acceptance to achieve 1% false rejection. The platform provides a configurable threshold system: each verification check (document, face match, liveness, watchlist) has its own threshold, and the composite decision threshold is configurable. The platform monitors both rates in real-time and can auto-tune thresholds within client-defined bounds. When false rejection rates spike (e.g., after a model update), the system alerts operators and can automatically roll back to the previous model version.
Key Numbers to Remember
| Metric | Value |
|---|---|
| False Accept Rate (FAR) | < 0.1% |
| False Rejection Rate (FRR) | < 2% |
| Face matching accuracy | 99.5%+ on benchmarks |
| Liveness detection accuracy | 99%+ attack detection |
| OCR accuracy (MRZ) | 99%+ character-level |
| Verification latency (90th percentile) | < 30 seconds |
| Manual review SLA | < 4 hours |
| Document template coverage | 10,000+ templates, 190+ countries |
| GDPR erasure SLA | 72 hours |
| AML retention period | 5 years minimum |
| ML model retraining frequency | Quarterly minimum |
| Monthly infrastructure cost | ~$26,200 (optimized: ~$16,600) |
Pre-Interview Checklist
- Understand the full KYC pipeline: document verification, biometrics, watchlist, AML
- Know how MRZ validation works (checksums, field extraction)
- Understand liveness detection techniques (passive, active, 3D depth)
- Be able to explain face matching (CNN embeddings, cosine similarity, threshold calibration)
- Know watchlist screening challenges (fuzzy name matching, transliteration)
- Understand AML risk scoring (weighted model, factor weights, decision thresholds)
- Know the KYC workflow state machine and idempotent step execution
- Understand tiered verification (Basic → Full, configurable per client)
- Be familiar with GDPR/CCPA requirements (right to erasure, data minimization)
- Know the conflict between GDPR erasure and AML retention
- Understand biometric data security (envelope encryption, key management)
- Know how to handle document fraud detection (JPEG forensics, ML-based detection)
- Be able to discuss deepfake countermeasures and the arms race
- Understand multi-country support and data residency requirements
- Know the monitoring strategy (system health + business metrics)