Processus de Verification
Guide complet du processus KYC : upload de documents, verification automatisee Onfido, webhook HMAC et workflow de revue administrateur.
Vue d'ensemble
Le processus de verification KYC YaniPay est concu en 5 etapes pour garantir a la fois la conformite reglementaire et une experience utilisateur fluide. L'integration Onfido permet une verification automatisee dans 95% des cas, avec un fallback sur la revue manuelle par les administrateurs.
Delai moyen
~3 min
Verification automatique
Taux de reussite
95%
Auto-approuve par Onfido
Securite
AES-256
Chiffrement au repos
Processus etape par etape
Upload des documents
L'utilisateur uploade sa piece d'identite (recto/verso), un justificatif de domicile et un selfie en direct.
POST /api/kyc/uploadValidation et soumission
L'API valide le format des fichiers, les metadonnees, et soumet le dossier a Onfido pour analyse automatique.
POST /api/kyc/validateAnalyse Onfido (IA)
Onfido verifie l'authenticite du document, la correspondance avec le selfie et detecte les tentatives de fraude.
Traitement automatiqueReception du webhook
Onfido envoie le resultat via webhook securise HMAC-SHA256. Le statut est mis a jour en base de donnees.
POST /api/webhooks/onfidoDecision finale
L'utilisateur est approuve automatiquement (95% des cas) ou le dossier passe en revue manuelle par un admin.
PUT /api/admin/kyc/:idPOST /api/kyc/upload
Upload un document d'identite. Accepte multipart/form-data. Les fichiers sont chiffres et stockes dans un bucket securise.
1 // Client-side upload 2 const formData = new FormData(); 3 formData.append('document', selectedFile); 4 formData.append('type', 'identity_card'); // "identity_card" | "passport" | "proof_of_address" | "selfie" 5 formData.append('side', 'front'); // "front" | "back" (requis pour identity_card) 6 7 const response = await fetch('/api/kyc/upload', { 8 method: 'POST', 9 body: formData, // No Content-Type header (browser sets boundary) 10 }); 11 12 // Response - 201 Created 13 { 14 "success": true, 15 "data": { 16 "documentId": "doc_abc123", 17 "type": "identity_card", 18 "side": "front", 19 "status": "uploaded", 20 "mimeType": "image/jpeg", 21 "size": 2456789, 22 "uploadedAt": "2024-12-20T10:00:00Z" 23 } 24 }
Formats acceptes
POST /api/kyc/validate
Valide l'ensemble du dossier KYC et soumet les documents a Onfido pour verification automatique.
1 // POST /api/kyc/validate 2 const response = await fetch('/api/kyc/validate', { 3 method: 'POST', 4 headers: { 'Content-Type': 'application/json' }, 5 body: JSON.stringify({ 6 documents: [ 7 { documentId: 'doc_abc123', type: 'identity_card', side: 'front' }, 8 { documentId: 'doc_def456', type: 'identity_card', side: 'back' }, 9 { documentId: 'doc_ghi789', type: 'proof_of_address' }, 10 { documentId: 'doc_jkl012', type: 'selfie' }, 11 ], 12 personalInfo: { 13 firstName: 'Jean', 14 lastName: 'Dupont', 15 dateOfBirth: '1990-05-15', 16 nationality: 'FR', 17 address: { 18 street: '42 rue de la Paix', 19 city: 'Paris', 20 postalCode: '75002', 21 country: 'FR', 22 }, 23 }, 24 }), 25 }); 26 27 // Response - 200 OK 28 { 29 "success": true, 30 "data": { 31 "verificationId": "ver_xyz789", 32 "status": "pending", 33 "onfidoCheckId": "chk_onfido_abc", 34 "submittedAt": "2024-12-20T10:05:00Z", 35 "estimatedCompletion": "2024-12-20T10:08:00Z" 36 } 37 }
GET /api/kyc/verification
Recupere le statut actuel de la verification KYC de l'utilisateur authentifie.
1 { 2 "success": true, 3 "data": { 4 "verificationId": "ver_xyz789", 5 "status": "VALIDATED", // "PENDING" | "IN_REVIEW" | "VALIDATED" | "REJECTED" | "EXPIRED" 6 "level": 2, // KYC level atteint 7 "documents": [ 8 { 9 "type": "identity_card", 10 "status": "verified", 11 "verifiedAt": "2024-12-20T10:07:00Z" 12 }, 13 { 14 "type": "proof_of_address", 15 "status": "verified", 16 "verifiedAt": "2024-12-20T10:07:00Z" 17 }, 18 { 19 "type": "selfie", 20 "status": "verified", 21 "verifiedAt": "2024-12-20T10:07:00Z" 22 } 23 ], 24 "checks": { 25 "documentAuthenticity": "pass", 26 "faceMatch": "pass", 27 "livenessDetection": "pass", 28 "fraudDetection": "pass" 29 }, 30 "approvedAt": "2024-12-20T10:07:30Z", 31 "expiresAt": "2029-12-20T10:07:30Z" // Validite 5 ans 32 } 33 }
GET /api/admin/kyc
Liste les verifications KYC pour l'interface d'administration. Filtrable par statut, date et niveau de risque. Requiert le role ADMIN.
GET /api/admin/kyc?status=IN_REVIEW&page=1&limit=20&sort=submittedAt:asc1 { 2 "success": true, 3 "data": { 4 "verifications": [ 5 { 6 "id": "ver_abc123", 7 "userId": "usr_def456", 8 "userName": "Jean D.", 9 "email": "jean.d@example.com", 10 "status": "IN_REVIEW", // ✓ enum Prisma : IN_REVIEW (pas "review") 11 "riskScore": 45, // 0-100, higher = riskier 12 "onfidoResult": "consider", // "clear" | "consider" | "unidentified" 13 "documents": ["identity_card", "proof_of_address", "selfie"], 14 "flags": ["address_mismatch"], 15 "submittedAt": "2024-12-20T09:30:00Z" 16 } 17 ], 18 "pagination": { 19 "page": 1, 20 "limit": 20, 21 "total": 3, 22 "totalPages": 1 23 }, 24 "stats": { 25 "pending": 2, 26 "review": 3, 27 "approvedToday": 47, 28 "rejectedToday": 1 29 } 30 } 31 }
PUT /api/admin/kyc/:id
Permet a un administrateur d'approuver ou rejeter une verification KYC apres revue manuelle des documents.
1 // Approuver une verification 2 await fetch('/api/admin/kyc/ver_abc123', { 3 method: 'PUT', 4 headers: { 'Content-Type': 'application/json' }, 5 body: JSON.stringify({ 6 action: 'approve', // "approve" | "reject" 7 level: 2, // KYC level accorde 8 notes: 'Documents conformes, adresse verifiee manuellement', 9 }), 10 }); 11 12 // Rejeter une verification 13 await fetch('/api/admin/kyc/ver_abc123', { 14 method: 'PUT', 15 headers: { 'Content-Type': 'application/json' }, 16 body: JSON.stringify({ 17 action: 'reject', 18 reason: 'document_expired', // Code de raison standardise 19 notes: 'Piece d\'identite expiree depuis le 01/01/2024', 20 allowRetry: true, // Autoriser une nouvelle soumission 21 }), 22 });
Webhook Onfido
Le webhook Onfido est securise par verification de signature HMAC-SHA256. Ce endpoint ne requiert pas d'authentification NextAuth.js ; la securite repose entierement sur la verification cryptographique de la signature.
1 import { NextResponse } from 'next/server'; 2 import crypto from 'crypto'; 3 import { prisma } from '@/lib/prisma'; 4 5 const ONFIDO_WEBHOOK_TOKEN = process.env.ONFIDO_WEBHOOK_TOKEN!; 6 7 export async function POST(request: Request) { 8 const body = await request.text(); 9 const headers = request.headers; 10 11 // Onfido Webhook - HMAC-SHA256 verification 12 const signature = headers.get('X-SHA2-Signature'); 13 const hmac = crypto.createHmac('sha256', ONFIDO_WEBHOOK_TOKEN); 14 hmac.update(body); 15 const expected = hmac.digest('hex'); 16 if (signature !== expected) { 17 return new Response('Unauthorized', { status: 401 }); 18 } 19 20 const event = JSON.parse(body); 21 const { resource_type, action, object } = event.payload; 22 23 if (resource_type === 'check' && action === 'check.completed') { 24 const checkResult = object.result; // "clear" | "consider" 25 const checkId = object.id; 26 27 // Update verification status based on Onfido result 28 const verification = await prisma.kycVerification.findFirst({ 29 where: { onfidoCheckId: checkId }, 30 }); 31 32 if (verification) { 33 if (checkResult === 'clear') { 34 // Auto-approve: all checks passed 35 await prisma.kycVerification.update({ 36 where: { id: verification.id }, 37 data: { 38 status: 'VALIDATED', // ✓ enum Prisma : VALIDATED (pas APPROVED) 39 onfidoResult: 'clear', 40 approvedAt: new Date(), 41 approvedBy: 'system:onfido', 42 }, 43 }); 44 45 // Upgrade user KYC level 46 await prisma.user.update({ 47 where: { id: verification.userId }, 48 data: { kycLevel: 2 }, 49 }); 50 } else { 51 // Flag for manual review 52 await prisma.kycVerification.update({ 53 where: { id: verification.id }, 54 data: { 55 status: 'IN_REVIEW', // ✓ enum Prisma : IN_REVIEW (pas REVIEW) 56 onfidoResult: checkResult, 57 flags: object.breakdown_flags || [], 58 }, 59 }); 60 } 61 } 62 } 63 64 return NextResponse.json({ received: true }); 65 }
Verification HMAC critique
Workflow admin
Lorsqu'Onfido retourne un resultat consider(5% des cas), le dossier passe en revue manuelle. L'administrateur dispose d'un dashboard dedie avec acces aux documents et au rapport Onfido.
Consulter le dashboard admin
Filtrer par status="IN_REVIEW" pour voir les dossiers en attente.
Examiner les documents
Verifier visuellement les documents, comparer avec le selfie, lire le rapport Onfido.
Analyser les flags
Onfido indique les raisons du "consider" : divergence d'adresse, qualite image, etc.
Prendre la decision
Approuver si les documents sont valides malgre les flags, ou rejeter avec raison et autoriser une nouvelle soumission.
Notifier l'utilisateur
L'utilisateur recoit un email et une notification push avec le resultat et les prochaines etapes.
SLA de revue
Gestion des donnees PII
Donnees personnelles sensibles
Chiffrement
- AES-256 au repos
- TLS 1.3 en transit
- Cles de chiffrement en HSM
Retention
- Conservation 5 ans (obligation legale)
- Suppression automatique apres expiration
- Droit d'acces et de rectification RGPD
1 import crypto from 'crypto'; 2 3 // Never log PII data 4 export function sanitizeKycLog(verification: KycVerification) { 5 return { 6 id: verification.id, 7 userId: verification.userId, 8 status: verification.status, 9 // NEVER include: documents, personalInfo, selfie, address 10 documentCount: verification.documents.length, 11 submittedAt: verification.submittedAt, 12 }; 13 } 14 15 // Encrypt document before storage 16 export function encryptDocument(buffer: Buffer, key: string): Buffer { 17 const iv = crypto.randomBytes(16); 18 const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key, 'base64'), iv); 19 const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]); 20 const authTag = cipher.getAuthTag(); 21 return Buffer.concat([iv, authTag, encrypted]); 22 } 23 24 // Mask sensitive data for admin display 25 export function maskName(name: string): string { 26 if (name.length <= 2) return name[0] + '*'; 27 return name[0] + '*'.repeat(name.length - 2) + name[name.length - 1]; 28 } 29 30 // Audit trail for all PII access 31 export async function logPiiAccess( 32 adminId: string, 33 verificationId: string, 34 action: 'view' | 'approve' | 'reject' 35 ) { 36 await prisma.auditLog.create({ 37 data: { 38 actorId: adminId, 39 action: `kyc_${action}`, 40 resourceType: 'kyc_verification', 41 resourceId: verificationId, 42 timestamp: new Date(), 43 ipAddress: getClientIp(), 44 }, 45 }); 46 }
Audit trail obligatoire
KYCStatus — valeurs Prisma correctes
KYCStatus Prisma accepte uniquement : PENDING | IN_REVIEW | VALIDATED | REJECTED | EXPIRED. Ne jamais utiliser APPROVED (non existant) ni REVIEW (forme incorrecte) — cela provoque une erreur Prisma en production.Prochaines etapes
Ressources associees
Derniere mise a jour : 9 avril 2026 — KYCStatus corriges (VALIDATED, IN_REVIEW — pas APPROVED/REVIEW), webhook Onfido HMAC-SHA256, workflow admin 5 etapes, PII AES-256 + audit trail