Pipeline biométrique — capture, OCR, MRZ, liveness, face match
Modules :
bio-svc(orchestrateur Temporal),mrz-svc,doc-auth-svc,liveness-svc,face-match-svc. Plusocr-svc(cf ADR-003).ADRs : ADR-001 (Temporal), ADR-003 (OCR), ADR-027 (fields DOC_KYC/SELFIE), ADR-028 (architecture pipeline).
POC : poc-mrz-parser (Kotlin, ICAO 9303 TD1/TD2/TD3 + 5 checksums).
Standards : ICAO 9303, NIST SP 800-63A IAL2/IAL3, ISO/IEC 30107-3 PAD, iBeta L1/L2, eIDAS LoA Substantial.
Ce document fixe la spec d’ingénierie complète de la pipeline biométrique VitaKYC. Un développeur qui le lit doit pouvoir, sans question résiduelle :
- Comprendre le flow E2E d’une vérification d’identité documentaire et biométrique (depuis capture jusqu’à décision).
- Implémenter chaque sous-service (
bio-svc,mrz-svc,doc-auth-svc,liveness-svc,face-match-svc) avec ses contrats d’API. - Câbler les fallbacks commerciaux (Regula, Onfido, Sumsub, Veriff, Jumio) sans dette d’intégration.
- Mesurer la qualité (TAR/FAR face match, PAD APCER/BPCER, AUC document authenticity).
- Respecter les obligations conformité (IAL2, BCT 2024, RGPD art. 9).
1. Vue d’ensemble
Section intitulée « 1. Vue d’ensemble »Flow nominal (~6–10 s p95) :
- Client capture document + selfie → upload chiffré
bio-svclance un workflow TemporalBiometricVerificationWorkflow- Étapes parallèles : OCR + MRZ + doc-auth + liveness + face match
- Cross-checks : OCR ↔ MRZ (nom, DOB), doc-auth ↔ MRZ (cohérence pays), liveness ↔ face match (même personne)
- Décision agrégée →
BiometricVerdict - Évent Kafka
bio.verdict.published→ consommé parrisk-matrix-svc(ajoute dimension AML/biométrique) etcase-mgmt-svc(route vers manuel si nécessaire)
2. Capture qualifiée — côté client
Section intitulée « 2. Capture qualifiée — côté client »2.1 Document
Section intitulée « 2.1 Document »| Type | Acquisition | Vérifications immédiates client |
|---|---|---|
| Passeport ICAO | recto (page photo + MRZ) + scan NFC optionnel | détection rectangulaire, MRZ visible, focus, glare < 5 % |
| CNI tunisienne | recto + verso | détection rectangulaire 2 faces, hologramme visible côté verso |
| CIN biométrique TN (depuis 2017) | recto + verso, NFC optionnel | idem + lecture NFC si supporté |
| Permis de conduire UE | recto + verso | détection des couleurs nationales spécifiques |
| Justificatif domicile (facture) | photo simple | OCR + cross-check adresse |
API Web SDK (extrait) :
const capture = await vitakyc.captureDocument({ documentType: "passport", // ou "cni_tn", "permis_eu", ... faces: ["front", "mrz_zone"], // requis selon le type qualityThresholds: { blurScore: 0.85, // Laplacian variance normalisé glareScore: 0.95, minResolutionDpi: 300, minFrameCoverage: 0.65 // doc occupe ≥ 65 % du frame }, retryPolicy: { maxAttempts: 3, hint: "show document fully in frame" }});// → { captureId, imageRefs[], qualityReport, deviceFingerprint }2.2 Selfie + liveness
Section intitulée « 2.2 Selfie + liveness »const selfie = await vitakyc.captureSelfie({ liveness: "active", // "passive" | "active" | "hybrid" challenge: ["blink", "turn_head_left", "smile"], videoLengthMs: 4000, // pour analyse temporelle PAD deviceFeatures: ["accelerometer", "gyroscope"] // anti-replay});// → { selfieId, videoRef, framesRef[], livenessSignals }Le SDK Web embarque un détecteur passif local (TensorFlow.js MobileFaceNet quantizé) qui pré-filtre les replays vidéo évidents avant upload — réduit le coût commercial.
3. Service bio-svc — orchestrateur Temporal
Section intitulée « 3. Service bio-svc — orchestrateur Temporal »3.1 Workflow
Section intitulée « 3.1 Workflow »@WorkflowInterfaceinterface BiometricVerificationWorkflow { @WorkflowMethod fun verify(input: BiometricInput): BiometricVerdict}
class BiometricVerificationWorkflowImpl : BiometricVerificationWorkflow { private val ocr = activityStub<OcrActivities>(timeout = 6.s, retries = 2) private val mrz = activityStub<MrzActivities>(timeout = 1.s, retries = 1) private val docAuth = activityStub<DocAuthActivities>(timeout = 8.s, retries = 2) private val liveness = activityStub<LivenessActivities>(timeout = 12.s, retries = 1) private val faceMatch = activityStub<FaceMatchActivities>(timeout = 4.s, retries = 2)
override fun verify(input: BiometricInput): BiometricVerdict { val (ocrResult, mrzResult, docAuthResult) = parallel( { ocr.extractFields(input.documentRef) }, { mrz.parseMrz(input.documentRef) }, { docAuth.checkAuthenticity(input.documentRef, input.documentType) } )
val crossCheck = crossCheckOcrMrz(ocrResult, mrzResult)
val (livenessResult, faceMatchResult) = parallel( { liveness.detect(input.selfieRef, input.livenessMode) }, { faceMatch.match(input.documentRef, input.selfieRef) } )
return aggregate( input = input, ocr = ocrResult, mrz = mrzResult, crossCheck = crossCheck, docAuth = docAuthResult, liveness = livenessResult, faceMatch = faceMatchResult ) }}3.2 Cross-checks et règles d’agrégation
Section intitulée « 3.2 Cross-checks et règles d’agrégation »| Cross-check | Règle | Effet sur verdict |
|---|---|---|
| OCR.surname ↔ MRZ.surname | Levenshtein distance ≤ 2 | OK ; sinon flag cross_check_name_mismatch |
| OCR.dateOfBirth ↔ MRZ.dateOfBirth | exact match | OK ; sinon flag cross_check_dob_mismatch |
| OCR.documentNumber ↔ MRZ.documentNumber | exact match | OK ; sinon flag cross_check_doc_no_mismatch |
| MRZ.country ↔ doc-auth.country | exact match | OK ; sinon flag cross_check_country_mismatch |
| face-match score | score_consolidated >= threshold(tenant) | OK ; sinon flag face_match_below_threshold |
| liveness verdict | live (PAD passe) | OK ; sinon flag liveness_failed |
3.3 Score consolidé
Section intitulée « 3.3 Score consolidé »score_final = ( w_ocr * ocr.confidence + w_mrz * mrz.checksumScore + w_doc * docAuth.score + w_liveness * liveness.score + w_face * faceMatch.scoreConsolidated) - sum(flag_penalties)
verdict = score_final >= threshold_pass ? PASS : score_final >= threshold_review ? MANUAL_REVIEW : FAILPoids et seuils configurables par tenant. Défaut MVP : w_ocr=0.10, w_mrz=0.20, w_doc=0.20, w_liveness=0.25, w_face=0.25. Seuils pass=0.80, review=0.60.
4. Service mrz-svc — parser ICAO 9303
Section intitulée « 4. Service mrz-svc — parser ICAO 9303 »4.1 Formats supportés
Section intitulée « 4.1 Formats supportés »| Format | Document type | Lignes × longueur | Champs |
|---|---|---|---|
| TD1 | CNI biométriques (incl. TN) | 3 lignes × 30 | doc#, country, surname, given names, DOB, sex, expiry, optional data + 5 check digits |
| TD2 | titres de séjour (souvent UE) | 2 lignes × 36 | idem TD1 (compact) |
| TD3 | passeports | 2 lignes × 44 | idem + nationalité distincte du pays émetteur |
4.2 Décodage et vérification
Section intitulée « 4.2 Décodage et vérification »Pour chaque format :
- Découpage des champs par offsets fixes (ICAO 9303-3, -4, -5).
- Conversion charset (
<filler → space, A-Z + 0-9 + chevrons). - Calcul des 5 chiffres de contrôle ICAO (algorithme 7-3-1) :
- sur le numéro de document
- sur la date de naissance
- sur la date d’expiration
- sur l’optional data
- composite final
- Si un seul check digit échoue → MRZ rejetée (pas de fallback OCR — la MRZ est déterministe ou absente).
4.3 Contrat de sortie
Section intitulée « 4.3 Contrat de sortie »data class MrzResult( val format: MrzFormat, // TD1, TD2, TD3 val documentNumber: String, val issuingCountry: IsoAlpha3, val nationality: IsoAlpha3?, // null pour TD1/TD2 val surname: String, val givenNames: String, val dateOfBirth: LocalDate, val sex: Sex, // M, F, X val expiryDate: LocalDate, val optionalData: String?, val checksumScore: Double, // 1.0 = tous OK ; 0.0 = composite KO val rawLines: List<String>, val warnings: List<MrzWarning>)POC référence : poc-mrz-parser (~400 LOC Kotlin pur, 18 tests verts dont les vecteurs ICAO publics et des MRZ réels TN/FR).
5. Service doc-auth-svc — authenticité document
Section intitulée « 5. Service doc-auth-svc — authenticité document »Le service vérifie qu’un document n’est ni falsifié, ni un imprimé, ni un assemblage d’images. Au MVP : adapter Regula Document Reader SDK (binaire on-prem inclus) + heuristiques internes pour les documents tunisiens.
5.1 Vérifications
Section intitulée « 5.1 Vérifications »| Vérification | Méthode | Score |
|---|---|---|
| Template matching (typeface, layout) | Regula DR SDK | templateScore ∈ [0,1] |
| Hologramme / OVI (Optically Variable Ink) | analyse multi-angle si capture vidéo | holoScore ∈ [0,1] |
| Patterns de fond (guilloché, microtexte) | Regula DR SDK | patternScore ∈ [0,1] |
| MRZ ↔ visible zone cohérence | déjà cross-check bio-svc | flag |
| Image manipulation (JPEG quantization, copy-move) | détecteur PRNU / ELA interne | tamperScore ∈ [0,1] |
| RFID NFC (passeport biométrique) | si disponible : Active Auth (AA) | aaScore ∈ {0, 1} |
5.2 Routage on-prem vs SaaS
Section intitulée « 5.2 Routage on-prem vs SaaS »| Mode | doc-auth-svc |
|---|---|
| SaaS | Regula Cloud API (recommandé) |
| On-prem connecté | Regula DR SDK on-prem (binaire ~600 MB) |
| Air-gap | heuristiques internes uniquement (templateScore réduit ; règle compensatoire côté Risk Matrix obligatoire) |
5.3 Modèles internes (V1)
Section intitulée « 5.3 Modèles internes (V1) »tamperScore reste interne dans toutes les modes — c’est un détecteur de manipulation d’image entraîné sur un dataset privé MENA. Model card minimale obligatoire (cf §9).
6. Service liveness-svc — détection PAD
Section intitulée « 6. Service liveness-svc — détection PAD »6.1 Mode hybride
Section intitulée « 6.1 Mode hybride »6.2 Métriques PAD (ISO/IEC 30107-3)
Section intitulée « 6.2 Métriques PAD (ISO/IEC 30107-3) »| Metric | Cible MVP | Cible V2 |
|---|---|---|
| APCER (Attack Presentation Classification Error Rate) — taux d’attaques classées comme bona fide | ≤ 5 % | ≤ 1 % |
| BPCER (Bona fide Presentation CER) — taux d’utilisateurs honnêtes rejetés | ≤ 3 % | ≤ 1,5 % |
| Latence p95 | ≤ 4 s | ≤ 2 s |
| iBeta certification | L2 (via fournisseur) | L2 interne |
6.3 Adapter par fournisseur
Section intitulée « 6.3 Adapter par fournisseur »| Fournisseur | iBeta | Mode | Latence | Coût/parcours |
|---|---|---|---|---|
| Onfido Motion | L1 + L2 | SaaS only | 2–3 s | 0,40 USD |
| Sumsub Liveness | L2 | SaaS + on-prem | 3–5 s | 0,30 USD |
| Veriff | L2 | SaaS only | 2–4 s | 0,50 USD |
| Regula Face SDK Liveness | L2 | SaaS + on-prem | 1–3 s | inclus dans licence DR |
| Interne MVP (passif) | non certifié | on-prem | 1–2 s | 0 |
Tenant sélectionne un fournisseur primaire + un fallback. Routage par feature flag, switchable sans redéploiement.
7. Service face-match-svc — InsightFace ArcFace + fallback
Section intitulée « 7. Service face-match-svc — InsightFace ArcFace + fallback »7.1 Algorithme interne
Section intitulée « 7.1 Algorithme interne »- Extraction du visage depuis le document (zone photo) via détecteur
RetinaFaceONNX. - Extraction du visage depuis le selfie (frame le plus net du video).
- Encodage chaque visage en vecteur 512-D via
ArcFace ResNet-100ONNX. - Score = cosine similarity entre les 2 vecteurs ∈ [-1, 1], normalisé en [0, 1].
- Seuil pass défaut tenant = 0,72 (TAR ~95 % @ FAR 0,01 % sur LFW + CFP-FP).
7.2 Score consolidé interne + commercial
Section intitulée « 7.2 Score consolidé interne + commercial »scoreConsolidated = w_intern * scoreArcFace + w_commercial * scoreCommercialSi commercial désactivé : w_intern = 1.0. Si scores divergent (|interne - commercial| > 0,15) → flag face_match_divergent → manual review.
7.3 Privacy
Section intitulée « 7.3 Privacy »- Aucun template biométrique transmis externalement (cf DPIA). Les vecteurs ArcFace restent dans
face-match-svc, jamais dans Kafka, jamais dansrisk-matrix. Seul le score scalaire est propagé. - Photo selfie stockée chiffrée KMS (AES-256-GCM, KEK per-tenant), retention 5 ans.
- Photo document stockée selon obligations BCT (10 ans pièce justificative).
8. API REST
Section intitulée « 8. API REST »8.1 Client SDK (synchrone polling ou webhook async)
Section intitulée « 8.1 Client SDK (synchrone polling ou webhook async) »| Méthode | Endpoint | Description |
|---|---|---|
POST | /v1/biometric/capture | upload chiffré document + selfie, lance workflow |
GET | /v1/biometric/verifications/:id | poll status pending / completed / failed |
GET | /v1/biometric/verifications/:id/verdict | récupère BiometricVerdict (audit complet) |
POST | /v1/biometric/verifications/:id/manual-decision | agent compliance tranche un cas manual_review_required |
8.2 Webhook (recommandé prod)
Section intitulée « 8.2 Webhook (recommandé prod) »POST <tenant_callback_url>X-VitaKYC-Signature: sha256=...{ "schema": "biometric.verdict.v1", "verification_id": "ver_abc123", "tenant_id": "TN-BANQUEX", "verdict": "PASS", "score_consolidated": 0.92, "subscores": { "ocr": 0.94, "mrz": 1.00, "doc_auth": 0.88, "liveness": 0.96, "face_match": 0.91 }, "flags": [], "completed_at": "2026-04-26T10:42:13Z"}8.3 OpenAPI
Section intitulée « 8.3 OpenAPI »Spec complète — voir /api/openapi/ section Biometric. Contract tests Pact en CI.
9. Model cards (obligatoire pour tout modèle ML)
Section intitulée « 9. Model cards (obligatoire pour tout modèle ML) »Chaque modèle ONNX déployé est accompagné d’un model card YAML inline conservé dans le repo bio-models/ :
modelId: face_match_arcface_resnet100version: 1.2.0hash: sha256:1d4f...trainingDataset: source: "MS-Celeb-1M (cleaned by InsightFace) + private TN/MENA augmentation" size: 5_800_000_images ethnicityBalance: - { region: NorthAfrica, share: 0.18 } - { region: MiddleEast, share: 0.15 } - { region: Europe, share: 0.32 } - { region: Asia, share: 0.22 } - { region: Other, share: 0.13 }metrics: TAR_at_FAR_0_001: 0.964 TAR_at_FAR_0_0001: 0.951 benchmark: ["LFW", "CFP-FP", "AgeDB-30", "Private-MENA-test-2026Q1"]intendedUse: KYC face match — document photo vs live selfielimitations: - "Performance dégradée sur visages partiellement occlus (>30%)" - "Performance dégradée si âge document - âge selfie > 8 ans"fairnessAssessment: byEthnicity: NorthAfrica: { TAR_FAR_0_001: 0.962 } MiddleEast: { TAR_FAR_0_001: 0.958 } Europe: { TAR_FAR_0_001: 0.971 } Asia: { TAR_FAR_0_001: 0.949 } byGender: M: { TAR_FAR_0_001: 0.965 } F: { TAR_FAR_0_001: 0.961 }approvedBy: ["compliance@vitakyc.io", "security@vitakyc.io"]deployedAt: 2026-04-25nextReviewAt: 2026-10-2510. Conformité
Section intitulée « 10. Conformité »| Référence | Exigence | Couverture |
|---|---|---|
| NIST SP 800-63A IAL2 | Identity proofing avec doc gov + biométrie | OCR + MRZ + face match + liveness ✓ |
| NIST SP 800-63A IAL3 | Idem + supervision (in-person ou supervised remote) | session live + agent compliance video V2 |
| eIDAS LoA Substantial | équivalent IAL2 | ✓ |
| eIDAS LoA High | doc à puce + face match certifié | NFC + Regula DA + face match certifié V2 |
| BCT Circulaire 2024 | onboarding distant banque exige liveness actif | iBeta L2 via fournisseur ✓ |
| RGPD art. 9 | données biométriques = catégorie spéciale | base légale consentement explicite + DPIA + retention limitée ✓ |
| ISO/IEC 30107-3 | PAD methodology | APCER/BPCER mesurés, certifié L2 fournisseur ✓ |
| ICAO 9303 | MRZ format et checksums | parser interne POC ✓ |
11. Performance et capacité
Section intitulée « 11. Performance et capacité »| Metric | Cible MVP | Cible V2 |
|---|---|---|
| Latence p95 pipeline complète | ≤ 8 s | ≤ 5 s |
Throughput / pod bio-svc | 25 verif/min | 80 verif/min |
| Coût marginal médian / parcours | ≤ 0,80 USD | ≤ 0,30 USD |
Disponibilité bio-svc | 99,5 % | 99,9 % |
| Taux fallback face match commercial | ≤ 25 % | ≤ 10 % |
12. Sécurité
Section intitulée « 12. Sécurité »- Chiffrement : photos document + selfie chiffrées AES-256-GCM avec DEK per-verification, KEK per-tenant via KMS Vault. Clés rotées tous les 12 mois (cf ADR-002 multi-tenant).
- Transit : mTLS entre
bio-svcet sous-services + adapters commerciaux. Cert pinning côté SDK Web. - Anti-replay : signature HMAC du payload de capture avec timestamp + sessionId ; rejet si timestamp > 5 min.
- Rate limit : 30 verif/min/tenant, 5/min/utilisateur (prévient brute force d’un agent compromis).
- Audit trail : chaque étape pipeline tracée append-only (event sourcing) avec
traceIdTemporal. Conservé 10 ans. - Anti-bot : reCAPTCHA Enterprise au début du parcours web. Sur mobile, Play Integrity + DeviceCheck.
13. Plan de migration MVP → V2
Section intitulée « 13. Plan de migration MVP → V2 »| Item | MVP (V0) | V2 (S+12) |
|---|---|---|
| MRZ parser interne | ✓ TD1/TD2/TD3 | + TD3 émetteurs rares (CN, SA, autres) |
| OCR interne | Tesseract + ONNX MENA | OCR transformer custom (better arabic) |
| doc-auth | Regula commercial (recommandé) | + heuristiques internes étendues |
| Liveness | commercial iBeta L2 | tentative internalisation iBeta L2 |
| Face match | InsightFace ArcFace ONNX | + Vision Transformer fine-tuné MENA |
| NFC passport reading | optionnel | activé pour eIDAS LoA High |
| PAD niveau 3 | non | oui (résistance masques 3D) |
| Deepfake detection | détecteur basique | détecteur 2026-grade (modèle dédié) |
14. Checklist go-live MVP
Section intitulée « 14. Checklist go-live MVP »-
mrz-svc: POC livré, déployé, contrat OpenAPI publié, 18 tests passants -
face-match-svc: modèle ArcFace ONNX déployé, model card validée, métriques mesurées sur Private-MENA-test -
liveness-svc: adapter Onfido + Sumsub configurés, switch tenant fonctionnel -
doc-auth-svc: Regula DR SDK on-prem installé chez tenant pilote -
bio-svc: workflow TemporalBiometricVerificationWorkflowen prod, 0 timeout sur 100 parcours pilote - Cross-checks OCR ↔ MRZ ↔ doc-auth opérationnels
- Audit trail append-only conservé 10 ans
- Photos chiffrées KMS, retention configurée 5 ans / 10 ans
- Aucun template biométrique sortant hors
face-match-svc - Webhook signé HMAC, retries, signature vérifiée côté tenant
- Métriques iBeta L2 documentées par fournisseur
- DPIA mise à jour pour traitement biométrique (cf DPIA)
- Runbook on-call : flow incident liveness commercial down, fallback dégradé
15. Références
Section intitulée « 15. Références »- ADR-001, ADR-003, ADR-027, ADR-028
- POC
poc-mrz-parser - DPIA traitement biométrique
- Standards : ICAO 9303, NIST SP 800-63A, ISO/IEC 30107-3, eIDAS
- Modèles publics : InsightFace, Tesseract 5
- Fournisseurs commerciaux : Regula, Onfido, Sumsub, Veriff, Jumio
Document de spec pipeline biométrique — version 1.0 (2026-04-26). Mises à jour bloquantes nécessitent un ADR.