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 — DocumentTier 2 — Document + visageTier 3 — Dossier complet
L'utilisateur fournitFormulaire + photo de la pièceFormulaire + photo de la pièce + selfieIdem Tier 2 + justificatif de domicile
OCR, champs, MRZ, expirationOuiOuiOui
Liveness du selfieNonOuiOui
Comparaison faciale pièce / selfieNonOuiOui
Justificatif de domicile (OCR)NonNonOui
Revue humaine systématiqueNonNonOui — jamais de décision auto
Ce que cela prouveLes informations saisies correspondent à la pièceEn plus : la personne correspond à la photo de la pièceDossier 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/subscription avec {"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

ChampDescription
referenceVotre identifiant interne (commande, utilisateur…). Renvoyé tel quel dans le résultat.
redirect_urlOù renvoyer l’utilisateur à la fin (http/https).
callback_urlURL 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

StatutSignification
En attenteSession créée, formulaire non soumis.
Analyse…Analyse en cours (quelques secondes).
AcceptéIdentité vérifiée automatiquement.
RejetéVisages incompatibles ou document expiré.
Revue manuelleDoute : un administrateur tranche.
À reprendrePhoto 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.

SituationDécision
distance < seuil_bas ET OCR fiable ET document valideAccepté
distance > seuil_hautRejeté
seuil_bas ≤ distance ≤ seuil_hautRevue manuelle
Confiance OCR trop basse, MRZ invalide, champs discordants, liveness faibleRevue manuelle (jamais d’acceptation automatique)
0 ou plusieurs visages, image inutilisableErreur 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

HTTPCas
401Clé API ou jeton invalide
403Droits insuffisants / compte bloqué
404Session introuvable (ou appartenant à un autre compte)
409Session déjà traitée
413 / 422Fichier trop gros / invalide ou formulaire incorrect
429Trop 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.