Verifica dal vivo
Pianifichi una sessione di verifica dell'identità dal vivo per il Suo cliente dal Suo sistema, la monitori, la annulli o la riprogrammi, e riceva per webhook la decisione del conduttore — il tutto identificato dal Suo codice di riferimento.
Una verifica dell'identità dal vivo è una sessione video in diretta condotta da una persona: un membro del Suo team (il conduttore) incontra il richiedente davanti alla telecamera, controlla il suo documento dal vivo e registra una decisione. Finora queste sessioni potevano essere pianificate solo dalla dashboard. Gli endpoint qui sotto permettono al Suo sistema di pianificare una sessione per uno dei Suoi clienti, di seguirla e di riceverne l'esito — tramite API e tramite webhook — identificata dal Suo codice di riferimento (externalRef).
Ogni endpoint si trova sotto /api/v1/kyc/conferences e si autentica con una chiave API (Authorization: Bearer ck_live_…). Vedi Autenticazione.
La verifica dell'identità dal vivo (questa pagina — una sessione video in diretta con un conduttore) è una funzione diversa da Live ID Validation (accesso biometrico per un utente di ritorno, vedi Live ID Validation).
Come funziona
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)
- La sessione viene pianificata per una richiesta di verifica esistente, oppure il richiedente viene creato in linea. Si indica il conduttore — un membro della Sua organizzazione che avvia la sessione dalla dashboard.
- Certivant invita il richiedente (email e/o SMS, nella sua lingua) e restituisce anche a Lei il link di accesso.
- Prima di entrare nella sessione, il richiedente risponde alla finestra di consenso. Il consenso alla registrazione video viene sempre raccolto; il resto della finestra può essere saltato con
consentScreen: falsequando i consensi sono già stati raccolti nella Sua app. - Quando il conduttore registra la decisione, Lei riceve
conference.completedcon la decisione, il suo effetto sulla verifica e il SuoexternalRef.
Prima di iniziare
| Requisito | Dove |
|---|---|
Chiave API con kyc:write (pianificare / modificare) e kyc:read (leggere) | KYC → Sviluppatore |
| Il Suo piano include le conferenze web | Il Suo abbonamento. Altrimenti 402 |
| Verifica dell'identità dal vivo attiva nelle impostazioni | KYC → Impostazioni → Verifica dell'identità dal vivo. Altrimenti 403 |
| Il Suo account è verificato da Certivant | Onboarding. Altrimenti 403 |
| Il conduttore ha il permesso di approvazione manuale | Il proprietario, oppure un ruolo / utente indicato nelle impostazioni di approvazione manuale. Altrimenti 403 |
| Un URL webhook (facoltativo, per gli esiti) | KYC → Impostazioni → Webhook — vedi Webhook → Configurazione |
Tutti gli orari sono in ISO-8601 con fuso orario — 2026-10-06T14:00:00Z oppure 2026-10-06T10:00:00-04:00. Un orario senza fuso orario viene rifiutato, così una sessione non può mai finire all'ora sbagliata a causa del fuso orario locale di un server. Le risposte sono sempre in UTC (…Z).
Pianifica una verifica dal vivo
POST /api/v1/kyc/conferences Ambito: kyc:write
Corpo della richiesta
{
"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 }
}
| Campo | Tipo | Obbligatorio | Note |
|---|---|---|---|
kycRequestId | stringa | uno dei due | Una richiesta di verifica esistente nella Sua organizzazione. |
applicant | oggetto | uno dei due | Crea in linea il richiedente e la verifica (vedi sotto). Un richiedente con la stessa email nella Sua organizzazione viene riutilizzato. |
host.userId / host.email | stringa | sì (uno dei due) | Il membro che conduce la sessione. Deve avere il permesso di approvazione manuale. |
externalRef | stringa | no | Il Suo codice di riferimento (ID ordine / pratica). Riportato in ogni risposta e in ogni webhook. Massimo 200. |
title | stringa | no | Mostrato al conduttore. Massimo 200. |
scheduledFor | data-ora ISO | uno dei due | Quando si svolge la sessione. Deve essere nel futuro. |
startNow | booleano | uno dei due | true = pianifica per adesso (il richiedente viene invitato subito). |
expiresAt | data-ora ISO | no | Fine della finestra di accesso. Predefinito: scheduledFor + 24 ore. |
language | enum | no | en, fr, es, de, it, pt, nl, pl, ro, tr, ar, zh, ja, ko, hi, vi, id, ru, uk, sv. Lingua dell'invito, salvata come lingua preferita del richiedente (il testo privo di traduzione in quella lingua viene inviato in inglese). Predefinito: la lingua salvata del richiedente, altrimenti la lingua predefinita della Sua organizzazione. |
consentScreen | booleano | no | Predefinito true — il richiedente conferma la condivisione di informazioni personali, l'acquisizione di foto e la registrazione video. false = la finestra chiede solo il consenso alla registrazione video, che non può mai essere disattivato. |
faceCheckSkippable | booleano | no | Omesso: l'impostazione del modulo della richiesta, altrimenti quella della Sua organizzazione (la verifica del volto è obbligatoria salvo modifica). false forza la verifica del volto come obbligatoria nella finestra di consenso. true offre l'alternativa non biometrica, solo dove la legge ne richiede una (ad es. Québec): il richiedente può rifiutare la verifica del volto, non vengono eseguiti né il selfie né la prova di vitalità e il conduttore confronta visivamente il volto con il documento d'identità durante la chiamata. La finestra non include la verifica del volto quando consentScreen è false. |
applicantFileUploadEnabled | booleano | no | Predefinito false — durante la sessione il richiedente può solo scattare foto con la fotocamera. true consente anche di inviare file dal proprio dispositivo. Il conduttore può modificarlo durante la sessione. |
channels.email / channels.sms | booleano | no | Come viene consegnato l'invito. Predefinito: email; SMS sul telefono di un richiedente appena creato. Per un richiedente esistente, il telefono registrato riceve l'SMS solo con sms: true. Entrambi false = non viene inviato nulla — consegni Lei applicantJoinUrl. |
Richiedente in linea (al posto di 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
}
Gli inviti via SMS vengono fatturati come gli inviti dalla dashboard (prima gli SMS inclusi nel piano, poi il saldo del credito). Se nessuno dei due è sufficiente, l'SMS viene saltato e l'email viene comunque inviata.
Esempio
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"
}'
Risposta 201
L'oggetto conferenza, più:
{
"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 viene restituito solo qui e nel reinvio dell'invito — Certivant conserva solo un hash del link, quindi non può essere riletto in seguito. applicantWasReused viene aggiunto per un richiedente creato in linea. invitation mostra i canali su cui l'invito è stato inviato.
Errori
| Stato | Quando |
|---|---|
400 | Validazione non riuscita (error.issues), entrambi o nessuno tra kycRequestId e applicant, entrambi o nessuno tra scheduledFor e startNow, un orario nel passato, expiresAt non successivo a scheduledFor, oppure il conduttore non è membro della Sua organizzazione. |
402 | Il Suo piano non include la verifica dal vivo. |
403 | La chiave non ha kyc:write; account in attesa di verifica; verifica dal vivo disattivata nelle impostazioni; oppure il conduttore non ha il permesso di approvazione manuale. |
404 | kycRequestId non esiste nella Sua organizzazione. |
409 | La verifica ha già una conferenza accessibile. Il corpo riporta il suo conferenceId — la riprogrammi o la annulli. Una POST ritentata quindi non invita mai il cliente due volte. |
Ottieni una verifica dal vivo
GET /api/v1/kyc/conferences/:id Ambito: kyc:read
Restituisce l'oggetto conferenza. Un ID che appartiene a un'altra organizzazione restituisce 404.
curl https://app.certivant.com/api/v1/kyc/conferences/cmf… \
-H "Authorization: Bearer $CERTIVANT_API_KEY"
Cerca tramite il Suo codice di riferimento
GET /api/v1/kyc/conferences/by-external-ref/:externalRef Ambito: kyc:read
La conferenza più recente associata a quel externalRef (codificato in URL). Restituisce 404 quando non ne esiste alcuna.
curl https://app.certivant.com/api/v1/kyc/conferences/by-external-ref/order-10042 \
-H "Authorization: Bearer $CERTIVANT_API_KEY"
Elenca le verifiche dal vivo
GET /api/v1/kyc/conferences Ambito: kyc:read
| Query | Note |
|---|---|
status | scheduled, in_progress, completed, cancelled oppure expired (una sessione ancora pianificata la cui finestra di accesso si è chiusa senza che la sessione avvenisse — un no-show). scheduled esclude quelle scadute. |
externalRef | Corrispondenza esatta sul Suo codice di riferimento. |
kycRequestId | Le conferenze di una singola verifica. |
scheduledFrom / scheduledTo | Date-ore ISO, estremi inclusi, su scheduledFor. |
createdFrom / createdTo | Date-ore ISO, estremi inclusi, sulla data di creazione. |
page / limit | Predefiniti 1 / 20; limit massimo 100. |
Ordinate dalla più recente, con un criterio di spareggio stabile, così la paginazione non salta né ripete mai una riga.
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 }
}
Riprogramma
POST /api/v1/kyc/conferences/:id/reschedule Ambito: 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"
}
Solo una conferenza scheduled può essere spostata (409 una volta che è iniziata, completata o annullata). Il nuovo orario deve essere nel futuro e prima della fine della finestra di accesso — passi un expiresAt successivo per estendere la finestra. title e externalRef sono facoltativi: li ometta per mantenerli, oppure invii null per cancellarli. Restituisce l'oggetto conferenza più previousScheduledFor, e attiva conference.rescheduled.
Riprogrammare non avvisa il richiedente — richiami il reinvio dell'invito per inviargli il nuovo orario.
Annulla
POST /api/v1/kyc/conferences/:id/cancel Ambito: kyc:write
{ "reason": "Customer cancelled the order" }
Il corpo è facoltativo. Annulla una conferenza scheduled: il link del richiedente smette di funzionare e viene attivato conference.cancelled con il Suo reason. Una sessione già in_progress restituisce 409 — è il conduttore a terminarla dalla sessione. La richiesta di verifica in sé non viene modificata.
Reinvia l'invito
POST /api/v1/kyc/conferences/:id/resend-invitation Ambito: kyc:write
{ "channels": { "email": true, "sms": false }, "language": "es" }
Emette un nuovo link di accesso (il precedente smette di funzionare) e rinvia l'invito sui channels — predefinito email, più SMS quando sul file è presente un telefono. Con entrambi i canali false non viene inviato nulla: usi Lei il applicantJoinUrl restituito (copyOnly: true). Consentito mentre la conferenza è scheduled o in_progress e la finestra di accesso è aperta; altrimenti 409.
L'oggetto conferenza
{
"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
}
| Campo | Note |
|---|---|
status | scheduled → in_progress (il conduttore è entrato) → completed (decisione registrata o sessione terminata), oppure cancelled. |
expired | true quando una sessione scheduled ha visto chiudersi la finestra di accesso senza che la sessione avvenisse (no-show). Si filtri con status=expired. |
consent.given | La risposta del richiedente principale: null finché non risponde, false se rifiuta (la sessione viene allora annullata). requiredPurposes contiene sempre video_recording. |
decision.decision | Il verdetto del conduttore: verified, rejected, needs_more_info oppure inconclusive. null finché non viene deciso. |
decision.outcome | Che effetto ha avuto sulla verifica: approved, rejected oppure escalated (needs_more_info / inconclusive la mettono in attesa di revisione). requestStatus è lo stato della verifica. |
recording.available | true quando la registrazione è stata assemblata e memorizzata o consegnata nel Suo storage. Il contenuto multimediale in sé non viene mai esposto da questa API. |
certificate | Il Certificato di verifica della verifica, quando ne è stato emesso uno: id, certificateNumber, verificationUrl. |
Webhook
Le verifiche dal vivo emettono cinque eventi verso il Suo URL webhook (l'URL specifico del modulo quando la verifica proviene da un modulo che ne ha uno, altrimenti quello predefinito della Sua organizzazione). Vengono attivati per ogni conferenza — pianificata dalla dashboard o dall'API — firmati e ritentati esattamente come ogni altro webhook di Certivant.
| Evento | Si attiva quando |
|---|---|
conference.scheduled | Viene creata una conferenza (pianificata per più tardi, oppure per adesso). |
conference.rescheduled | Una conferenza pianificata viene spostata a un nuovo orario. Riporta previousScheduledFor. |
conference.cancelled | Una conferenza viene annullata — tramite la Sua chiamata API, oppure perché il richiedente ha rifiutato il consenso. Riporta cancellation. |
conference.started | Il conduttore avvia la sessione (primo accesso). Una volta per avvio — le riconnessioni del conduttore non lo ripetono. |
conference.completed | Il conduttore registra la decisione finale. Riporta decision. |
Un reinvio dell'invito non è un nuovo conference.scheduled.
Non esiste ancora un evento no_show. Una sessione che non si svolge resta scheduled e viene segnalata con expired: true quando la sua finestra di accesso si chiude — interroghi GET /conferences?status=expired per trovare i no-show.
Payload
A differenza degli eventi kyc.request.*, gli eventi di conferenza usano un corpo con la forma della conferenza. Ogni evento riporta gli ID, il Suo externalRef, lo stato e i timestamp:
{
"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"
}
Campi specifici dell'evento:
conference.rescheduled—previousScheduledFor.conference.cancelled—cancellation.reasonecancellation.cancelledBy(api,host,applicant,system). Un consenso rifiutato arriva comecancelledBy: "applicant",reason: "applicant_refused_consent".conference.completed—decision(come nell'oggetto conferenza). La sessione potrebbe essere ancora in corso quando la decisione viene registrata, quindiendedAtpuò esserenull, e la registrazione è di norma ancora in fase di assemblaggio (recording.available: false) — legga più tardiGET /conferences/:idper lo stato finale della registrazione.
I payload non contengono mai contenuti multimediali, link alle registrazioni o token di accesso.
Verifica la firma
Ogni consegna è firmata con il segreto di firma del Suo webhook. L'header consigliato è X-Certivant-Signature-V2: t=<unix>,v1=<hex>, dove v1 è l'HMAC-SHA256 di `${t}.${rawBody}`. Rifiuti i timestamp che differiscono di più di 300 secondi e deduplichi su X-Certivant-Event-Id. (Gli header precedenti X-KYC-Signature / X-KYC-Timestamp / X-KYC-Event-Id continuano a essere inviati.)
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);
}
Dettagli completi e altre lingue: Verifica della firma.
Limiti di frequenza
Questi endpoint condividono il limite di frequenza API della Sua organizzazione con il resto dell'API v1 — vedi Limiti di frequenza.
Sessioni ospitate
Generi un URL a breve scadenza con cui il richiedente completa l'acquisizione dei documenti, il controllo di vivacità e le fasi di identificazione.
Lista di blocco
Aggiunga, elenchi e rimuova voci della lista di blocco dal Suo sistema — indirizzi email, numeri di telefono, numeri di documento e indirizzi IP o intervalli, oppure il volto, i dispositivi e l'IP dietro una verifica esistente.