Riferimento APIVerifica dal vivo

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)
  1. 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.
  2. Certivant invita il richiedente (email e/o SMS, nella sua lingua) e restituisce anche a Lei il link di accesso.
  3. 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: false quando i consensi sono già stati raccolti nella Sua app.
  4. Quando il conduttore registra la decisione, Lei riceve conference.completed con la decisione, il suo effetto sulla verifica e il Suo externalRef.

Prima di iniziare

RequisitoDove
Chiave API con kyc:write (pianificare / modificare) e kyc:read (leggere)KYC → Sviluppatore
Il Suo piano include le conferenze webIl Suo abbonamento. Altrimenti 402
Verifica dell'identità dal vivo attiva nelle impostazioniKYC → Impostazioni → Verifica dell'identità dal vivo. Altrimenti 403
Il Suo account è verificato da CertivantOnboarding. Altrimenti 403
Il conduttore ha il permesso di approvazione manualeIl 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 }
}
CampoTipoObbligatorioNote
kycRequestIdstringauno dei dueUna richiesta di verifica esistente nella Sua organizzazione.
applicantoggettouno dei dueCrea in linea il richiedente e la verifica (vedi sotto). Un richiedente con la stessa email nella Sua organizzazione viene riutilizzato.
host.userId / host.emailstringasì (uno dei due)Il membro che conduce la sessione. Deve avere il permesso di approvazione manuale.
externalRefstringanoIl Suo codice di riferimento (ID ordine / pratica). Riportato in ogni risposta e in ogni webhook. Massimo 200.
titlestringanoMostrato al conduttore. Massimo 200.
scheduledFordata-ora ISOuno dei dueQuando si svolge la sessione. Deve essere nel futuro.
startNowbooleanouno dei duetrue = pianifica per adesso (il richiedente viene invitato subito).
expiresAtdata-ora ISOnoFine della finestra di accesso. Predefinito: scheduledFor + 24 ore.
languageenumnoen, 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.
consentScreenbooleanonoPredefinito 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.
faceCheckSkippablebooleanonoOmesso: 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.
applicantFileUploadEnabledbooleanonoPredefinito 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.smsbooleanonoCome 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

StatoQuando
400Validazione 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.
402Il Suo piano non include la verifica dal vivo.
403La 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.
404kycRequestId non esiste nella Sua organizzazione.
409La 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

QueryNote
statusscheduled, 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.
externalRefCorrispondenza esatta sul Suo codice di riferimento.
kycRequestIdLe conferenze di una singola verifica.
scheduledFrom / scheduledToDate-ore ISO, estremi inclusi, su scheduledFor.
createdFrom / createdToDate-ore ISO, estremi inclusi, sulla data di creazione.
page / limitPredefiniti 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
}
CampoNote
statusscheduled → in_progress (il conduttore è entrato) → completed (decisione registrata o sessione terminata), oppure cancelled.
expiredtrue quando una sessione scheduled ha visto chiudersi la finestra di accesso senza che la sessione avvenisse (no-show). Si filtri con status=expired.
consent.givenLa risposta del richiedente principale: null finché non risponde, false se rifiuta (la sessione viene allora annullata). requiredPurposes contiene sempre video_recording.
decision.decisionIl verdetto del conduttore: verified, rejected, needs_more_info oppure inconclusive. null finché non viene deciso.
decision.outcomeChe 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.availabletrue quando la registrazione è stata assemblata e memorizzata o consegnata nel Suo storage. Il contenuto multimediale in sé non viene mai esposto da questa API.
certificateIl 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.

EventoSi attiva quando
conference.scheduledViene creata una conferenza (pianificata per più tardi, oppure per adesso).
conference.rescheduledUna conferenza pianificata viene spostata a un nuovo orario. Riporta previousScheduledFor.
conference.cancelledUna conferenza viene annullata — tramite la Sua chiamata API, oppure perché il richiedente ha rifiutato il consenso. Riporta cancellation.
conference.startedIl conduttore avvia la sessione (primo accesso). Una volta per avvio — le riconnessioni del conduttore non lo ripetono.
conference.completedIl 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.reason e cancellation.cancelledBy (api, host, applicant, system). Un consenso rifiutato arriva come cancelledBy: "applicant", reason: "applicant_refused_consent".
  • conference.completed — decision (come nell'oggetto conferenza). La sessione potrebbe essere ancora in corso quando la decisione viene registrata, quindi endedAt può essere null, e la registrazione è di norma ancora in fase di assemblaggio (recording.available: false) — legga più tardi GET /conferences/:id per 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.