Vérification faciale et détection de vivacité auto-hébergées pour le KYC
L'onboarding KYC (livreurs, magasins, clients) devait vérifier qu'un selfie en direct correspond bien à la photo d'identité — et qu'il ne s'agit pas d'une photo imprimée ou d'un écran — sans envoyer de données biométriques à une API cloud tierce, et sans qu'un faux signal IA ne bloque ou n'approuve silencieusement quelqu'un.
Microservice Python/FastAPI auto-hébergé exécutant tout le pipeline localement avec des modèles ONNX open-source : YuNet détecte le visage, MiniFASNet évalue la vivacité à partir d'une seule image (visage réel vs. photo imprimée vs. reflet d'écran), et SFace génère des empreintes comparées par similarité cosinus à la photo d'identité. Les trois scores forment un score de risque unique (pass/review/fail) — mais le service reste strictement consultatif : appelé de façon asynchrone, il "fail open" en cas de panne et ne remplace jamais la décision finale d'un humain.
The service sits entirely off the public path: it's not routed through the Rust gateway and has no exposed port on Traefik — it's called service-to-service, over the internal Docker network, authenticated with a shared internal token. admin_service's KYC controller stores the selfie and ID photo, then queues a job (VerifyFaceJob) that hits the FastAPI service, which pulls the images back itself by URL rather than receiving raw bytes.
Three lightweight ONNX models run in sequence, chosen specifically because they're built for fast CPU-only inference (no GPU on this VPS): YuNet for face detection, MiniFASNet (two variants, fused) for passive single-image liveness, and SFace for a 128-dimension face embedding compared by cosine similarity. Quality (blur/brightness/pose), liveness, and match scores are averaged into one risk score, thresholded into pass/review/fail.
Crucially, the AI service persists nothing itself — no images, no embeddings. Only the derived scores and status are written back onto the user's record in admin_service, which limits how much biometric data exists anywhere in the system. Model weights are pulled from public open-source sources (OpenCV Zoo, Silent-Face-Anti-Spoofing) at build time with checksum verification, rather than from a paid vision API.
Stack
Défis
Running three CNN-based inference steps per verification with no GPU meant picking architectures built for that constraint from the start (YuNet, SFace and MiniFASNet are all designed for sub-100ms CPU inference) rather than reaching for heavier, more "accurate on paper" models that would have made the KYC flow feel slow.
Passive, single-image liveness detection is inherently weaker than multi-frame or challenge-response liveness (blink, turn your head). Rather than treating it as a hard pass/fail gate, it's fused with quality and match scores into one risk score, and the whole system is designed fail-open and advisory — a liveness false negative degrades to a manual review, it never locks a real user out or silently approves a fake one.
Honest gap: the pipeline has no automated test suite today. Correctness currently leans on the downstream human review step rather than on regression tests for the CV models — a natural next hardening step.
Impact