Référence de l'APIAPI de vérification en direct

API de vérification en direct

Planifiez une session de vérification d'identité en direct pour votre client depuis votre propre système, suivez-la, annulez-la ou reprogrammez-la, et recevez la décision de l'animateur par webhook — le tout associé à votre propre code de référence.

Une vérification d'identité en direct est une session vidéo en direct animée par un humain : un membre de votre équipe (l'animateur) rencontre le demandeur par caméra, vérifie sa pièce d'identité en direct et enregistre une décision. Jusqu'à présent, ces sessions ne pouvaient être planifiées que depuis le tableau de bord. Les points de terminaison ci-dessous permettent à votre système de planifier une session pour l'un de vos clients, de la suivre et de recevoir le résultat, par API et par webhook, le tout associé à votre propre code de référence (externalRef).

Tous les points de terminaison se trouvent sous /api/v1/kyc/conferences et sont authentifiés par une clé d'API (Authorization: Bearer ck_live_…). Voir Authentification.

La vérification d'identité en direct (cette page : une session vidéo en direct avec un animateur humain) est une fonctionnalité différente de Live ID Validation (ouverture de session biométrique pour un utilisateur récurrent, voir Live ID Validation).

Fonctionnement

sequenceDiagram
  participant You as Your system
  participant C as Certivant
  participant A as Applicant
  participant H as Host (your team)
  You->>C: POST /conferences (externalRef, host, scheduledFor)
  C-->>You: 201 conference + applicantJoinUrl
  C->>A: Invitation email / SMS (join link)
  C-->>You: webhook conference.scheduled
  H->>C: Joins from the dashboard
  C-->>You: webhook conference.started
  A->>C: Consents + joins the session
  H->>C: Records the decision
  C-->>You: webhook conference.completed (decision, externalRef)
  1. Vous planifiez la session pour une demande de vérification existante, ou vous créez le demandeur et la vérification en ligne. Vous désignez l'animateur : un membre de votre organisation qui conduit la session depuis le tableau de bord.
  2. Certivant invite le demandeur (par courriel et/ou SMS, dans sa langue) et vous renvoie également le lien de connexion.
  3. Avant d'entrer dans la session, le demandeur répond à la fenêtre de consentement. Le consentement à l'enregistrement vidéo est toujours recueilli ; le reste de la fenêtre peut être ignoré avec consentScreen: false lorsque vous avez déjà recueilli ces consentements dans votre propre application.
  4. Lorsque l'animateur enregistre la décision, vous recevez conference.completed avec la décision, son effet sur la vérification et votre externalRef.

Avant de commencer

ExigenceOù
Clé d'API avec kyc:write (planifier / modifier) et kyc:read (lire)KYC → Développeur
Votre forfait inclut la visioconférenceVotre abonnement. Sinon, 402
Vérification d'identité en direct activée dans vos paramètresKYC → Paramètres → Vérification d’identité en direct. Sinon, 403
Votre compte est vérifié par CertivantIntégration. Sinon, 403
L'animateur dispose de l'autorisation d'approbation manuelleLe propriétaire, ou un rôle / un utilisateur figurant dans vos paramètres d'approbation manuelle. Sinon, 403
Une URL de webhook (facultatif, pour les résultats)KYC → Paramètres → Webhooks : voir Webhooks → Configuration

Toutes les dates et heures sont au format ISO-8601 avec fuseau horaire : 2026-10-06T14:00:00Z ou 2026-10-06T10:00:00-04:00. Une date-heure sans fuseau horaire est refusée, afin qu'une session ne puisse jamais tomber à la mauvaise heure à cause du fuseau local d'un serveur. Les réponses sont toujours en UTC (…Z).


Planifier une vérification en direct

POST /api/v1/kyc/conferences    Portée : kyc:write

Corps de la requête

{
  "kycRequestId": "req_01J…",
  "host": { "email": "compliance@yourcompany.com" },
  "externalRef": "order-10042",
  "title": "Account opening — video check",
  "scheduledFor": "2026-10-06T10:00:00-04:00",
  "language": "fr",
  "consentScreen": true,
  "applicantFileUploadEnabled": false,
  "channels": { "email": true, "sms": true }
}
ChampTypeObligatoireRemarques
kycRequestIdchaînel'un des deuxUne demande de vérification existante dans votre organisation.
applicantobjetl'un des deuxCrée le demandeur et la vérification en ligne à la place (voir ci-dessous). Un demandeur ayant le même courriel dans votre organisation est réutilisé.
host.userId / host.emailchaîneoui (l'un des deux)Le membre qui conduit la session. Doit disposer de l'autorisation d'approbation manuelle.
externalRefchaînenonVotre code de référence (identifiant de commande ou de dossier). Repris dans chaque réponse et chaque webhook. 200 caractères maximum.
titlechaînenonAffiché à l'animateur. 200 caractères maximum.
scheduledFordate-heure ISOl'un des deuxMoment où la session a lieu. Doit être dans le futur.
startNowbooléenl'un des deuxtrue = planifier pour maintenant (le demandeur est invité immédiatement).
expiresAtdate-heure ISOnonFin de la fenêtre de connexion. Par défaut : scheduledFor + 24 h.
languageénumérationnonen, fr, es, de, it, pt, nl, pl, ro, tr, ar, zh, ja, ko, hi, vi, id, ru, uk, sv. Langue de l'invitation, enregistrée comme langue préférée du demandeur (tout texte sans traduction dans cette langue est envoyé en anglais). Par défaut : la langue enregistrée du demandeur, sinon la langue par défaut de votre organisation.
consentScreenbooléennonPar défaut true : le demandeur confirme le partage de renseignements personnels, la prise de photo et l'enregistrement vidéo. false = la fenêtre demande uniquement le consentement à l'enregistrement vidéo, qui ne peut jamais être désactivé.
faceCheckSkippablebooléennonOmis : le réglage du formulaire de la demande, sinon celui de votre organisation (la vérification du visage est obligatoire sauf modification). false force la vérification du visage obligatoire dans la fenêtre de consentement. true offre l'alternative non biométrique, seulement là où la loi en exige une (p. ex. au Québec) : le demandeur peut refuser la vérification du visage, aucun selfie ni détection du vivant n'est exécuté, et l'hôte compare visuellement le visage avec la pièce d'identité pendant l'appel. La fenêtre n'inclut pas la vérification du visage lorsque consentScreen vaut false.
applicantFileUploadEnabledbooléennonPar défaut false : pendant la session, le demandeur ne peut prendre des photos qu'avec sa caméra. true autorise aussi l'envoi de fichiers depuis son appareil. L'animateur peut modifier ce paramètre pendant la session.
channels.email / channels.smsbooléennonMode de remise de l'invitation. Par défaut : courriel ; SMS au téléphone d'un demandeur nouvellement créé. Pour un demandeur existant, son téléphone enregistré ne reçoit un SMS que si sms: true. Les deux à false = rien n'est envoyé : vous transmettez vous-même applicantJoinUrl.

Demandeur en ligne (à la place de kycRequestId) :

{
  "applicant": {
    "fullName": "Jane Doe",
    "email": "jane@example.com",
    "phone": "+14155551234",
    "emailVerified": false,
    "phoneVerified": false,
    "externalRef": "customer-881"
  },
  "host": { "userId": "usr_01H…" },
  "externalRef": "order-10042",
  "startNow": true
}

Les invitations par SMS sont facturées comme les invitations du tableau de bord (d'abord les SMS inclus dans votre forfait, puis votre solde de crédits). Si aucun des deux ne couvre le coût, le SMS est ignoré et le courriel est tout de même envoyé.

Exemple

curl -X POST https://app.certivant.com/api/v1/kyc/conferences \
  -H "Authorization: Bearer $CERTIVANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kycRequestId": "req_01J...",
    "host": { "email": "compliance@yourcompany.com" },
    "externalRef": "order-10042",
    "scheduledFor": "2026-10-06T10:00:00-04:00",
    "language": "en"
  }'

Réponse 201

L'objet de conférence, plus :

{
  "id": "cmf…",
  "kycRequestId": "req_01J…",
  "externalRef": "order-10042",
  "status": "scheduled",
  "scheduledFor": "2026-10-06T14:00:00.000Z",
  "expiresAt": "2026-10-07T14:00:00.000Z",
  "applicantJoinUrl": "https://app.certivant.com/forms/conference/3f9c…",
  "invitation": { "email": true, "sms": false }
}

applicantJoinUrl n'est renvoyé qu'ici et par renvoi de l'invitation : Certivant ne conserve qu'un haché du lien, il ne peut donc pas être relu plus tard. applicantWasReused est ajouté pour un demandeur en ligne. invitation indique les canaux sur lesquels l'invitation a été envoyée.

Erreurs

StatutQuand
400Validation échouée (error.issues), kycRequestId et applicant tous deux présents ou tous deux absents, scheduledFor et startNow tous deux présents ou tous deux absents, une heure passée, expiresAt non postérieur à scheduledFor, ou l'animateur n'est pas membre de votre organisation.
402Votre forfait n'inclut pas la vérification en direct.
403La clé ne possède pas kyc:write ; compte en attente de vérification ; vérification en direct désactivée dans les paramètres ; ou l'animateur n'a pas l'autorisation d'approbation manuelle.
404kycRequestId n'existe pas dans votre organisation.
409La vérification a déjà une conférence accessible. Le corps contient son conferenceId : reprogrammez ou annulez-la plutôt. Une requête POST relancée n'invite donc jamais le client deux fois.

Obtenir une vérification en direct

GET /api/v1/kyc/conferences/:id    Portée : kyc:read

Renvoie l'objet de conférence. Un identifiant appartenant à une autre organisation renvoie un 404.

curl https://app.certivant.com/api/v1/kyc/conferences/cmf… \
  -H "Authorization: Bearer $CERTIVANT_API_KEY"

Rechercher par votre code de référence

GET /api/v1/kyc/conferences/by-external-ref/:externalRef    Portée : kyc:read

La conférence la plus récente portant ce externalRef (encodé pour une URL). Renvoie 404 si aucune n'existe.

curl https://app.certivant.com/api/v1/kyc/conferences/by-external-ref/order-10042 \
  -H "Authorization: Bearer $CERTIVANT_API_KEY"

Lister les vérifications en direct

GET /api/v1/kyc/conferences    Portée : kyc:read

RequêteRemarques
statusscheduled, in_progress, completed, cancelled ou expired (une session encore planifiée dont la fenêtre de connexion s'est refermée sans que la session ait eu lieu : un défaut de présentation). scheduled exclut les sessions expirées.
externalRefCorrespondance exacte sur votre code de référence.
kycRequestIdConférences d'une même vérification.
scheduledFrom / scheduledToDate-heures ISO, bornes incluses, sur scheduledFor.
createdFrom / createdToDate-heures ISO, bornes incluses, sur la date de création.
page / limitPar défaut 1 / 20, limit maximum 100.

Du plus récent au plus ancien, avec un critère de départage stable afin que la pagination n'ignore ni ne répète jamais une ligne.

curl "https://app.certivant.com/api/v1/kyc/conferences?status=completed&createdFrom=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $CERTIVANT_API_KEY"
{
  "data": [ { "id": "cmf…", "status": "completed", "externalRef": "order-10042" } ],
  "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}

Reprogrammer

POST /api/v1/kyc/conferences/:id/reschedule    Portée : kyc:write

{
  "scheduledFor": "2026-10-08T15:30:00+02:00",
  "expiresAt": "2026-10-09T15:30:00+02:00",
  "title": "Rescheduled video check",
  "externalRef": "order-10042"
}

Seule une conférence scheduled peut être déplacée (409 une fois qu'elle a commencé, qu'elle est terminée ou qu'elle a été annulée). La nouvelle heure doit être dans le futur et avant la fin de la fenêtre de connexion : passez un expiresAt plus tardif pour prolonger la fenêtre. title et externalRef sont facultatifs : omettez-les pour les conserver, passez null pour les effacer. Renvoie l'objet de conférence accompagné de previousScheduledFor, et déclenche conference.rescheduled.

La reprogrammation ne prévient pas le demandeur : appelez renvoi de l'invitation pour lui envoyer la nouvelle heure.

Annuler

POST /api/v1/kyc/conferences/:id/cancel    Portée : kyc:write

{ "reason": "Customer cancelled the order" }

Le corps est facultatif. Annule une conférence scheduled : le lien du demandeur cesse de fonctionner et conference.cancelled est déclenché avec votre reason. Une session déjà in_progress renvoie 409 : l'animateur y met fin depuis la session. La demande de vérification elle-même reste inchangée.

Renvoyer l'invitation

POST /api/v1/kyc/conferences/:id/resend-invitation    Portée : kyc:write

{ "channels": { "email": true, "sms": false }, "language": "es" }

Émet un nouveau lien de connexion (l'ancien cesse de fonctionner) et renvoie l'invitation sur les channels : par défaut le courriel, plus le SMS lorsqu'un téléphone est enregistré. Si les deux canaux sont à false, rien n'est envoyé : utilisez vous-même le applicantJoinUrl renvoyé (copyOnly: true). Autorisé tant que la conférence est scheduled ou in_progress et que sa fenêtre de connexion est ouverte, sinon 409.


L'objet de conférence

{
  "id": "cmf…",
  "kycRequestId": "req_01J…",
  "applicantId": "cma…",
  "organizationId": "org_01H…",
  "externalRef": "order-10042",
  "title": "Account opening — video check",
  "status": "completed",
  "expired": false,
  "scheduledFor": "2026-10-06T14:00:00.000Z",
  "expiresAt": "2026-10-07T14:00:00.000Z",
  "startedAt": "2026-10-06T14:01:12.000Z",
  "endedAt": "2026-10-06T14:14:40.000Z",
  "durationSeconds": 808,
  "createdAt": "2026-10-01T09:00:00.000Z",
  "updatedAt": "2026-10-06T14:14:40.000Z",
  "host": { "userId": "usr_01H…", "name": "Sam Reviewer", "email": "compliance@yourcompany.com" },
  "applicant": {
    "id": "cma…",
    "fullName": "Jane Doe",
    "email": "jane@example.com",
    "externalRef": "customer-881",
    "joinedAt": "2026-10-06T14:02:03.000Z"
  },
  "additionalApplicants": [],
  "additionalKycRequestIds": [],
  "consent": {
    "screenEnabled": true,
    "requiredPurposes": ["share_information", "photo_capture", "video_recording"],
    "given": true,
    "at": "2026-10-06T14:01:58.000Z",
    "version": "2026-10-04"
  },
  "decision": {
    "decision": "verified",
    "outcome": "approved",
    "requestStatus": "APPROVED",
    "notes": "ID checked on camera, matches the applicant.",
    "decidedByUserId": "usr_01H…",
    "documentDecisions": { "verified": 2, "rejected": 0, "needsResubmission": 0 }
  },
  "recording": { "available": true, "deliveryStatus": "delivered", "durationSeconds": 790, "sha256": "9b1f…" },
  "certificate": null
}
ChampRemarques
statusscheduled → in_progress (l'animateur a rejoint) → completed (décision enregistrée ou session terminée), ou cancelled.
expiredtrue lorsqu'une session scheduled a vu sa fenêtre de connexion se refermer sans avoir eu lieu (défaut de présentation). Filtrez avec status=expired.
consent.givenLa réponse du demandeur principal : null tant qu'il n'a pas répondu, false s'il a refusé (la session est alors annulée). requiredPurposes contient toujours video_recording.
decision.decisionLe verdict de l'animateur : verified, rejected, needs_more_info ou inconclusive. null tant qu'aucune décision n'a été prise.
decision.outcomeEffet sur la vérification : approved, rejected ou escalated (needs_more_info / inconclusive la mettent en attente d'examen). requestStatus est le statut de la vérification.
recording.availabletrue une fois l'enregistrement assemblé et stocké ou livré à votre stockage. Le média lui-même n'est jamais exposé par cette API.
certificateLe certificat de vérification de la vérification lorsqu'il a été émis : id, certificateNumber, verificationUrl.

Webhooks

Les vérifications en direct émettent cinq événements vers votre URL de webhook (l'URL propre au formulaire lorsque la vérification provient d'un formulaire qui en définit une, sinon l'URL par défaut de votre organisation). Ils sont déclenchés pour chaque conférence, qu'elle ait été planifiée depuis le tableau de bord ou par l'API, signés et relancés exactement comme tous les autres webhooks Certivant.

ÉvénementDéclenché lorsque
conference.scheduledUne conférence est créée (planifiée pour plus tard, ou démarrée immédiatement).
conference.rescheduledUne conférence planifiée est déplacée à une nouvelle heure. Contient previousScheduledFor.
conference.cancelledUne conférence est annulée, par votre appel API ou parce que le demandeur a refusé le consentement. Contient cancellation.
conference.startedL'animateur démarre la session (première connexion). Une fois par démarrage : les reconnexions de l'animateur ne le répètent pas.
conference.completedL'animateur enregistre la décision finale. Contient decision.

Un renvoi d'invitation n'est pas un nouvel événement conference.scheduled.

Il n'existe pas encore d'événement no_show. Une session qui n'a jamais lieu reste scheduled et est signalée avec expired: true une fois sa fenêtre de connexion refermée : interrogez GET /conferences?status=expired pour repérer les défauts de présentation.

Charge utile

Contrairement aux événements kyc.request.*, les événements de conférence utilisent un corps au format de conférence. Chaque événement contient les identifiants, votre externalRef, le statut et les horodatages :

{
  "event": "conference.completed",
  "conferenceId": "cmf…",
  "kycRequestId": "req_01J…",
  "applicantId": "cma…",
  "organizationId": "org_01H…",
  "externalRef": "order-10042",
  "title": "Account opening — video check",
  "status": "completed",
  "scheduledFor": "2026-10-06T14:00:00.000Z",
  "expiresAt": "2026-10-07T14:00:00.000Z",
  "startedAt": "2026-10-06T14:01:12.000Z",
  "endedAt": null,
  "durationSeconds": null,
  "createdAt": "2026-10-01T09:00:00.000Z",
  "updatedAt": "2026-10-06T14:13:55.000Z",
  "host": { "userId": "usr_01H…", "name": "Sam Reviewer", "email": "compliance@yourcompany.com" },
  "consent": {
    "screenEnabled": true,
    "requiredPurposes": ["share_information", "photo_capture", "video_recording"],
    "given": true,
    "at": "2026-10-06T14:01:58.000Z"
  },
  "decision": {
    "decision": "rejected",
    "outcome": "rejected",
    "requestStatus": "REJECTED",
    "notes": "Face does not match the ID photo.",
    "decidedByUserId": "usr_01H…",
    "documentDecisions": { "verified": 0, "rejected": 1, "needsResubmission": 0 }
  },
  "recording": { "available": false, "deliveryStatus": "pending", "durationSeconds": null, "sha256": null },
  "certificate": null,
  "timestamp": "2026-10-06T14:13:55.000Z"
}

Champs propres à chaque événement :

  • conference.rescheduled : previousScheduledFor.
  • conference.cancelled : cancellation.reason et cancellation.cancelledBy (api, host, applicant, system). Un consentement refusé arrive avec cancelledBy: "applicant", reason: "applicant_refused_consent".
  • conference.completed : decision (comme dans l'objet de conférence). La session peut encore être en cours lorsque la décision est enregistrée ; endedAt peut donc valoir null, et l'enregistrement est généralement encore en cours d'assemblage (recording.available: false) : lisez GET /conferences/:id plus tard pour connaître l'état final de l'enregistrement.

Les charges utiles ne contiennent jamais de média, de liens d'enregistrement ni de jetons de connexion.

Vérifier la signature

Chaque livraison est signée avec le secret de signature de votre webhook. L'en-tête recommandé est X-Certivant-Signature-V2: t=<unix>,v1=<hex>, où v1 est le HMAC-SHA256 de `${t}.${rawBody}`. Rejetez les horodatages décalés de plus de 300 secondes et dédoublonnez à partir de X-Certivant-Event-Id. (Les anciens en-têtes X-KYC-Signature / X-KYC-Timestamp / X-KYC-Event-Id sont toujours envoyés.)

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCertivantWebhook(rawBody: string, headers: Headers, secret: string) {
  const parts = (headers.get("X-Certivant-Signature-V2") ?? "").split(",").map((p) => p.split("="));
  const t = parts.find(([k]) => k === "t")?.[1] ?? "";
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return parts.some(
    ([k, v]) => k === "v1" && v?.length === expected.length && timingSafeEqual(Buffer.from(v), Buffer.from(expected)),
  );
}

// In your handler:
const event = JSON.parse(rawBody);
if (event.event === "conference.completed") {
  await orders.markIdentityChecked(event.externalRef, event.decision.outcome);
}

Détails complets et autres langages : Vérification de la signature.


Limites de débit

Ces points de terminaison partagent la limite de débit de l'API de votre organisation avec le reste de l'API v1 : voir Limites de débit.