Documentation VerifID
Tout ce qu’il faut pour intégrer la vérification d’identité dans votre application.
Introduction
VerifID vérifie qu’une personne est bien celle qu’elle prétend être. À partir d’un formulaire (identité + pièce + selfie), le service lit le document (OCR), compare les champs lus aux champs saisis, contrôle la zone MRZ, vérifie que le selfie est réel (liveness) et compare le visage du selfie à celui du document.
Deux modes d’utilisation : depuis le dashboard (vous lancez vous-même une vérification) ou via l’API (votre application crée une session et redirige l’utilisateur vers notre formulaire hébergé).
Niveaux de vérification : Tier 1, 2 et 3
Trois niveaux sont proposés. Vous choisissez le niveau à chaque vérification lancée depuis le dashboard ; pour l'API, il est porté par la clé API.
| Tier 1 — Document | Tier 2 — Document + visage | Tier 3 — Dossier complet | |
|---|---|---|---|
| L'utilisateur fournit | Formulaire + photo de la pièce | Formulaire + photo de la pièce + selfie | Idem Tier 2 + justificatif de domicile |
| OCR, champs, MRZ, expiration | Oui | Oui | Oui |
| Liveness du selfie | Non | Oui | Oui |
| Comparaison faciale pièce / selfie | Non | Oui | Oui |
| Justificatif de domicile (OCR) | Non | Non | Oui |
| Revue humaine systématique | Non | Non | Oui — jamais de décision auto |
| Ce que cela prouve | Les informations saisies correspondent à la pièce | En plus : la personne correspond à la photo de la pièce | Dossier complet validé par un agent humain |
À savoir : le Tier 1 ne confirme pas que la personne est le titulaire de la pièce (aucune comparaison de visage). Le Tier 3 ajoute le justificatif de domicile et garantit qu'un agent examine toujours le dossier — c'est le niveau d'assurance maximal (KYC renforcé).
Une clé API par niveau
Dans Clés API & intégration, choisissez le niveau en créant la clé. Une clé Tier 1 ne crée que des sessions Tier 1 ; idem pour les Tiers 2 et 3. Le niveau n'est jamais lu dans la requête : un paramètre tier envoyé par votre application est ignoré. Une clé ne voit (GET /sessions) que les sessions de son niveau. Le Tier 3 exige un plan Pro ou supérieur.
Plans & quotas
Chaque entreprise a un plan (GET /api/v1/plans pour le catalogue public). Le plan définit le quota mensuel de vérifications et le tier maximal autorisé : Free et Starter → Tier 2, Pro et Enterprise → Tier 3.
- Quota dépassé →
402 Payment Required; tier non couvert par le plan →403 Forbidden. - Votre consommation du mois :
GET /api/v1/me/usage(quota: -1= illimité). - Changer de plan :
POST /api/v1/me/subscriptionavec{"plan_slug": "pro"}— les upgrades sont validés par notre équipe, le retour au Free est immédiat.
Démarrage rapide
- Créez un compte puis ouvrez Clés API & intégration dans le dashboard.
- Générez une clé API du niveau voulu (Tier 1 ou Tier 2, affichée une seule fois) et notez votre secret de webhook.
- Créez une session depuis votre serveur, redirigez l’utilisateur, recevez le résultat.
curl -X POST https://api.votredomaine.com/api/v1/sessions \
-H "X-API-Key: vk_live_xxx" -H "Content-Type: application/json" \
-d '{"reference":"order-42","redirect_url":"https://monapp.com/retour","callback_url":"https://monapp.com/hooks/verifid"}'Authentification
API externe : en-tête X-API-Key: vk_live_…. Les clés sont stockées hachées (SHA-256) ; une clé perdue ne peut pas être retrouvée, seulement révoquée puis remplacée. N’exposez jamais une clé côté navigateur ou mobile.
Dashboard : JWT (/auth/login, jeton d’accès 30 min + jeton de rafraîchissement 7 jours). Le formulaire hébergé est protégé par le jeton à usage unique contenu dans verification_url.
Créer une session
POST /api/v1/sessions
| Champ | Description |
|---|---|
reference | Votre identifiant interne (commande, utilisateur…). Renvoyé tel quel dans le résultat. |
redirect_url | Où renvoyer l’utilisateur à la fin (http/https). |
callback_url | URL de webhook appelée quand le résultat est prêt (https public en production). |
{
"id": "6f1c…", "status": "created", "reference": "order-42",
"verification_url": "https://votredomaine.com/verify/AbCd…",
"created_at": "2026-10-08T10:00:00Z"
}Une session expire après 24 h et ne peut être soumise qu’une fois (sauf si l’utilisateur doit reprendre une photo : statut retry).
Formulaire hébergé
Redirigez l’utilisateur vers verification_url. Il saisit nom, prénoms, date de naissance, numéro de pièce, nationalité, date d’expiration, choisit le type de pièce, importe la photo du document et, au Tier 2 seulement, prend un selfie. À la fin, il est renvoyé vers redirect_url avec les paramètres reference et status.
Important : les paramètres de l’URL de retour sont modifiables par l’utilisateur. Ne les utilisez que pour l’affichage ; la décision doit toujours reposer sur le webhook ou sur GET /sessions/{id} appelé depuis votre serveur.
Récupérer le résultat
GET /api/v1/sessions/{id} (clé API)
{ "id": "6f1c…", "status": "accepted", "reference": "order-42",
"reasons": [], "error_code": "", "completed_at": "2026-10-08T10:00:07Z" }Les scores détaillés, le texte OCR et les fichiers restent réservés au dashboard et à l’administration : l’API ne renvoie que la décision et ses motifs.
Webhooks
Appel POST en JSON dès que la session atteint accepted, rejected ou manual_review (puis de nouveau après une décision manuelle). Réessais exponentiels en cas d’échec. Répondez en 2xx rapidement.
POST /hooks/verifid
X-VerifID-Timestamp: 1791453607
X-VerifID-Signature: sha256=9b74c9897bac770ffc029102a200c5de…
{"event":"verification.completed","session_id":"6f1c…","reference":"order-42","tier":2,"status":"accepted","reasons":[]}Vérifier la signature (Python)
import hmac, hashlib, time
def verify(secret: str, timestamp: str, body: bytes, header: str) -> bool:
if abs(time.time() - int(timestamp)) > 300: # anti-rejeu
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", header)Vérifier la signature (Node.js)
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(secret, timestamp, rawBody, header) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const mac = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex')
return mac.length === header.length && timingSafeEqual(Buffer.from(mac), Buffer.from(header))
}Statuts & raisons
| Statut | Signification |
|---|---|
| En attente | Session créée, formulaire non soumis. |
| Analyse… | Analyse en cours (quelques secondes). |
| Accepté | Identité vérifiée automatiquement. |
| Rejeté | Visages incompatibles ou document expiré. |
| Revue manuelle | Doute : un administrateur tranche. |
| À reprendre | Photo inutilisable : l’utilisateur la reprend. |
| Expiré | Session non soumise dans les 24 h. |
Raisons possibles : FACE_MISMATCH, FACE_UNCERTAIN, OCR_LOW_CONFIDENCE, FIELDS_MISMATCH, MRZ_INVALID, LIVENESS_LOW, DOCUMENT_EXPIRED, MANUAL_DECISION.
Règle de décision
Au Tier 2, toutes les lignes ci-dessous s'appliquent. Au Tier 1, seules s'appliquent celles qui ne concernent pas le visage : pas de distance cosinus, pas de liveness ; un document expiré est rejeté, et tout doute sur la lecture, la MRZ ou les champs envoie en revue manuelle.
Le score facial est une distance cosinus = 1 − similarité cosinus : proche de 0 = visages semblables. Cette convention est identique dans le code, les tests et cette documentation ; l’inverser retournerait le sens des seuils.
| Situation | Décision |
|---|---|
distance < seuil_bas ET OCR fiable ET document valide | Accepté |
distance > seuil_haut | Rejeté |
seuil_bas ≤ distance ≤ seuil_haut | Revue manuelle |
| Confiance OCR trop basse, MRZ invalide, champs discordants, liveness faible | Revue manuelle (jamais d’acceptation automatique) |
| 0 ou plusieurs visages, image inutilisable | Erreur claire, l’utilisateur reprend la photo |
| Document expiré | Rejeté |
La concordance des champs est le minimum des champs critiques (nom, prénoms, date de naissance, numéro de pièce, expiration), pas leur moyenne : un seul champ faux suffit à écarter l'acceptation automatique.
Bonnes photos
- Document à plat, entier, bien éclairé, sans reflet ni doigt sur le texte ; zone MRZ visible.
- Tiers 2 et 3 : selfie de face, visage dégagé (ni lunettes de soleil ni masque), un seul visage, différent de la photo du document.
- Tier 3 : justificatif de domicile récent (< 3 mois), entier, nom et adresse lisibles.
- Formats JPEG, PNG, WEBP · 8 Mo max · 200 px minimum de côté (600 px pour la pièce).
Erreurs
| HTTP | Cas |
|---|---|
| 401 | Clé API ou jeton invalide |
| 403 | Droits insuffisants / compte bloqué |
| 404 | Session introuvable (ou appartenant à un autre compte) |
| 409 | Session déjà traitée |
| 413 / 422 | Fichier trop gros / invalide ou formulaire incorrect |
| 429 | Trop de requêtes |
Codes de reprise (status = retry) : NO_FACE_DOCUMENT, NO_FACE_SELFIE, MULTIPLE_FACES_DOCUMENT, MULTIPLE_FACES_SELFIE, IMAGE_UNREADABLE, IMAGE_TOO_SMALL.
Sécurité & données personnelles
- Pièces et selfies chiffrés au repos ; accès administrateur aux fichiers tracé dans le journal d’audit.
- Suppression automatique des fichiers après une durée de rétention configurable (30 jours par défaut) ; seul le résultat est conservé.
- Webhooks signés HMAC-SHA256, protection anti-SSRF sur les URLs de rappel.
- Mots de passe hachés (bcrypt), limitation de débit, journaux JSON avec identifiant de requête (
X-Request-ID).
Les données d’identité sont des données personnelles sensibles : définissez votre base légale, vos durées de conservation et informez les utilisateurs (RGPD ou loi camerounaise n° 2024/017 selon votre cas).
Calibration des seuils
Les seuils (FACE_THRESHOLD_LOW, FACE_THRESHOLD_HIGH, OCR_MIN_CONFIDENCE, FIELD_MATCH_MIN, LIVENESS_MIN) sont configurables par variables d’environnement. Les valeurs par défaut sont provisoires : calibrez-les sur un jeu de test représentatif de vos vrais documents et appareils, en traçant la courbe ROC et en mesurant le FAR (fausses acceptations) et le FRR (faux rejets).
Un seuil bas strict réduit les fausses acceptations mais envoie plus de dossiers en revue manuelle ; un seuil haut trop bas rejette à tort des personnes légitimes.