API de verificação ao vivo

Agende uma sessão de verificação de identidade ao vivo para o seu cliente a partir do seu próprio sistema, acompanhe, cancele ou reagende a sessão e receba a decisão do anfitrião por webhook — tudo identificado pelo seu próprio código de referência.

Uma verificação de identidade ao vivo é uma sessão de vídeo ao vivo conduzida por um anfitrião humano: um membro da sua equipe (o anfitrião) atende o solicitante por câmera, confere o documento dele ao vivo e registra uma decisão. Até agora, essas sessões só podiam ser agendadas pelo painel. Os endpoints abaixo permitem que o seu sistema agende uma sessão para um dos seus clientes, acompanhe a sessão e receba o resultado — por API e por webhook — identificado pelo seu próprio código de referência (externalRef).

Todos os endpoints ficam em /api/v1/kyc/conferences e são autenticados com uma chave de API (Authorization: Bearer ck_live_…). Consulte Autenticação.

A verificação Live ID (esta página — uma sessão de vídeo ao vivo com um anfitrião humano) é um recurso diferente da Live ID Validation (logon biométrico para um usuário que retorna, veja Live ID Validation).

Como funciona

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. Você agenda a sessão para uma solicitação de verificação existente ou cria o solicitante em linha. Você indica o anfitrião — um membro da sua organização que conduz a sessão pelo painel.
  2. A Certivant convida o solicitante (e-mail e/ou SMS, no idioma dele) e também devolve o link de acesso para você.
  3. Antes de entrar na sessão, o solicitante responde à tela de consentimento. O consentimento para gravação em vídeo é sempre coletado; o restante da tela pode ser ignorado com consentScreen: false quando você já tiver coletado esses consentimentos no seu próprio app.
  4. Quando o anfitrião registra a decisão, você recebe conference.completed com a decisão, o efeito dela na verificação e o seu externalRef.

Antes de começar

RequisitoOnde
Chave de API com kyc:write (agendar / alterar) e kyc:read (consultar)KYC → Desenvolvedor
Seu plano inclui videoconferênciaSua assinatura. Caso contrário, 402
Verificação de identidade ao vivo habilitada nas suas configuraçõesKYC → Configurações → Verificação de identidade ao vivo. Caso contrário, 403
Sua conta está verificada pela CertivantProcesso de onboarding. Caso contrário, 403
O anfitrião tem permissão de aprovação manualO proprietário, ou uma função / usuário listado nas suas configurações de aprovação manual. Caso contrário, 403
Uma URL de webhook (opcional, para os resultados)KYC → Configurações → Webhooks — veja Webhooks → Configuração

Todas as datas e horas são ISO-8601 com fuso horário — 2026-10-06T14:00:00Z ou 2026-10-06T10:00:00-04:00. Uma data e hora sem fuso horário é rejeitada, para que uma sessão nunca caia no horário errado por causa do fuso local de um servidor. As respostas são sempre em UTC (…Z).


Agendar uma verificação ao vivo

POST /api/v1/kyc/conferences    Escopo: kyc:write

Corpo da requisição

{
  "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 }
}
CampoTipoObrigatórioObservações
kycRequestIdstringum dos doisUma solicitação de verificação existente na sua organização.
applicantobjectum dos doisCrie o solicitante + a verificação em linha (veja abaixo). Um solicitante com o mesmo e-mail na sua organização é reaproveitado.
host.userId / host.emailstringsim (um deles)O membro que conduz a sessão. Deve ter permissão de aprovação manual.
externalRefstringnãoSeu código de referência (ID do pedido / caso). Repetido em todas as respostas e em todos os webhooks. Máximo de 200.
titlestringnãoExibido para o anfitrião. Máximo de 200.
scheduledFordata e hora ISOum dos doisQuando a sessão acontece. Deve estar no futuro.
startNowbooleanum dos doistrue = agendar para agora (o solicitante é convidado imediatamente).
expiresAtdata e hora ISOnãoFim da janela de acesso. Padrão: scheduledFor + 24 h.
languageenumnãoen, fr, es, de, it, pt, nl, pl, ro, tr, ar, zh, ja, ko, hi, vi, id, ru, uk, sv. Idioma do convite, salvo como o idioma preferido do solicitante (qualquer texto sem tradução nesse idioma é enviado em inglês). Padrão: o idioma salvo do solicitante; se não houver, o idioma padrão da sua organização.
consentScreenbooleannãoPadrão true — o solicitante confirma o compartilhamento de informações pessoais, a captura de foto e a gravação em vídeo. false = a tela pede somente o consentimento para gravação em vídeo, que nunca pode ser desativado.
faceCheckSkippablebooleannãoOmitido: a configuração do formulário da solicitação, senão a da sua organização (a verificação facial é obrigatória, a menos que você a altere). false força a verificação facial como obrigatória na janela de consentimento. true oferece a alternativa não biométrica, somente onde a lei a exigir (p. ex. Quebec): o solicitante pode recusar a verificação facial, nenhum selfie nem prova de vida é executado, e o anfitrião compara visualmente o rosto com o documento de identidade durante a chamada. A janela não inclui a verificação facial quando consentScreen é false.
applicantFileUploadEnabledbooleannãoPadrão false — durante a sessão, o solicitante só pode tirar fotos com a câmera. true também permite enviar arquivos do dispositivo. O anfitrião pode alterar isso durante a sessão.
channels.email / channels.smsbooleannãoComo o convite é entregue. Padrão: e-mail; SMS para o telefone de um solicitante recém-criado. Para um solicitante existente, o telefone cadastrado recebe SMS somente quando sms: true. Ambos false = nada é enviado — entregue você mesmo o applicantJoinUrl.

Solicitante em linha (em vez 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
}

Convites por SMS são cobrados como os convites do painel (primeiro o SMS incluído no seu plano, depois o saldo de créditos). Se nenhum dos dois cobrir, o SMS é ignorado e o e-mail ainda é enviado.

Exemplo

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"
  }'

Resposta 201

O objeto da sessão, além de:

{
  "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 é retornado somente aqui e em reenviar convite — a Certivant armazena apenas um hash do link, então ele não pode ser lido depois. applicantWasReused é adicionado para um solicitante em linha. invitation mostra os canais pelos quais o convite foi enviado.

Erros

StatusQuando
400Validação falhou (error.issues), ambos ou nenhum entre kycRequestId e applicant, ambos ou nenhum entre scheduledFor e startNow, horário no passado, expiresAt não posterior a scheduledFor, ou o anfitrião não é membro da sua organização.
402Seu plano não inclui verificação ao vivo.
403A chave não tem kyc:write; conta pendente de verificação; verificação ao vivo desabilitada nas configurações; ou o anfitrião não tem permissão de aprovação manual.
404kycRequestId não existe na sua organização.
409A verificação já tem uma sessão ativa que ainda pode ser acessada. O corpo traz o conferenceId dela — reagende ou cancele essa sessão em vez disso. Uma nova tentativa de POST, portanto, nunca convida o cliente duas vezes.

Obter uma verificação ao vivo

GET /api/v1/kyc/conferences/:id    Escopo: kyc:read

Retorna o objeto da sessão. Um ID que pertença a outra organização retorna 404.

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

Consultar pelo seu código de referência

GET /api/v1/kyc/conferences/by-external-ref/:externalRef    Escopo: kyc:read

A sessão mais recente que contém esse externalRef (codifique-o para URL). Retorna 404 quando não existe nenhuma.

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

Listar verificações ao vivo

GET /api/v1/kyc/conferences    Escopo: kyc:read

ConsultaObservações
statusscheduled, in_progress, completed, cancelled ou expired (uma sessão ainda agendada cuja janela de acesso foi encerrada sem que a sessão acontecesse — um não comparecimento). scheduled exclui as expiradas.
externalRefCorrespondência exata com o seu código de referência.
kycRequestIdSessões de uma verificação.
scheduledFrom / scheduledToDatas e horas ISO, inclusivas, em scheduledFor.
createdFrom / createdToDatas e horas ISO, inclusivas, na data de criação.
page / limitPadrão 1 / 20, limit máximo 100.

Das mais recentes para as mais antigas, com um desempate estável para que a paginação nunca pule ou repita uma linha.

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 }
}

Reagendar

POST /api/v1/kyc/conferences/:id/reschedule    Escopo: 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"
}

Somente uma sessão scheduled pode ser movida (409 depois que ela começou, foi concluída ou cancelada). O novo horário deve estar no futuro e antes do fim da janela de acesso — passe um expiresAt posterior para estender a janela. title e externalRef são opcionais: omita para manter, ou envie null para limpar. Retorna o objeto da sessão mais previousScheduledFor e dispara conference.rescheduled.

Reagendar não notifica o solicitante — chame reenviar convite para enviar a ele o novo horário.

Cancelar

POST /api/v1/kyc/conferences/:id/cancel    Escopo: kyc:write

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

O corpo é opcional. Cancela uma sessão scheduled: o link do solicitante deixa de funcionar e conference.cancelled é disparado com o seu reason. Uma sessão que já esteja in_progress retorna 409 — o anfitrião a encerra pela própria sessão. A solicitação de verificação em si permanece inalterada.

Reenviar o convite

POST /api/v1/kyc/conferences/:id/resend-invitation    Escopo: kyc:write

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

Gera um novo link de acesso (o anterior deixa de funcionar) e reenvia o convite pelos channels — padrão e-mail, mais SMS quando há telefone cadastrado. Com ambos os canais false, nada é enviado: use você mesmo o applicantJoinUrl retornado (copyOnly: true). Permitido enquanto a sessão estiver scheduled ou in_progress e a janela de acesso estiver aberta; caso contrário, 409.


O objeto da sessão

{
  "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
}
CampoObservações
statusscheduled → in_progress (o anfitrião entrou) → completed (decisão registrada ou sessão encerrada), ou cancelled.
expiredtrue quando uma sessão scheduled teve sua janela de acesso encerrada sem que a sessão acontecesse (não comparecimento). Filtre com status=expired.
consent.givenA resposta do solicitante principal: null até que ele responda, false se ele recusar (a sessão é então cancelada). requiredPurposes sempre contém video_recording.
decision.decisionO veredito do anfitrião: verified, rejected, needs_more_info ou inconclusive. null até a decisão.
decision.outcomeO efeito sobre a verificação: approved, rejected ou escalated (needs_more_info / inconclusive a deixam em revisão). requestStatus é o status da verificação.
recording.availabletrue quando a gravação está montada e armazenada ou entregue ao seu armazenamento. A mídia em si nunca é exposta por esta API.
certificateO Certificado de Verificação da verificação, quando um foi emitido: id, certificateNumber, verificationUrl.

Webhooks

As verificações ao vivo emitem cinco eventos para a sua URL de webhook (a URL própria do formulário, quando a verificação veio de um formulário que tenha uma; caso contrário, o padrão da sua organização). Eles são disparados para todas as sessões — agendadas pelo painel ou pela API — assinados e reenviados exatamente como todos os outros webhooks da Certivant.

EventoDisparado quando
conference.scheduledUma sessão é criada (agendada para depois, ou iniciada agora).
conference.rescheduledUma sessão agendada muda para um novo horário. Traz previousScheduledFor.
conference.cancelledUma sessão é cancelada — por sua chamada de API ou porque o solicitante recusou o consentimento. Traz cancellation.
conference.startedO anfitrião inicia a sessão (primeira entrada). Uma vez por início — reconexões do anfitrião não repetem o evento.
conference.completedO anfitrião registra a decisão final. Traz decision.

Um reenvio de convite não é um novo conference.scheduled.

Ainda não existe um evento no_show. Uma sessão que nunca acontece permanece scheduled e é reportada com expired: true quando a sua janela de acesso se encerra — consulte GET /conferences?status=expired para encontrar os não comparecimentos.

Payload

Diferentemente dos eventos kyc.request.*, os eventos de sessão usam um corpo no formato de sessão. Cada evento traz os IDs, o seu externalRef, o status e os carimbos de data e hora:

{
  "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"
}

Campos específicos de cada evento:

  • conference.rescheduled — previousScheduledFor.
  • conference.cancelled — cancellation.reason e cancellation.cancelledBy (api, host, applicant, system). Um consentimento recusado chega como cancelledBy: "applicant", reason: "applicant_refused_consent".
  • conference.completed — decision (como no objeto da sessão). A sessão ainda pode estar em andamento quando a decisão é registrada, então endedAt pode ser null, e a gravação costuma ainda estar sendo montada (recording.available: false) — leia GET /conferences/:id depois para ver o estado final da gravação.

Os payloads nunca contêm mídia, links de gravação ou tokens de acesso.

Verificar a assinatura

Cada entrega é assinada com o seu segredo de assinatura de webhook. O cabeçalho recomendado é X-Certivant-Signature-V2: t=<unix>,v1=<hex>, em que v1 é o HMAC-SHA256 de `${t}.${rawBody}`. Rejeite timestamps com mais de 300 segundos de diferença e elimine duplicatas com base em X-Certivant-Event-Id. (Os cabeçalhos mais antigos X-KYC-Signature / X-KYC-Timestamp / X-KYC-Event-Id ainda são enviados.)

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);
}

Detalhes completos e em outros idiomas: Verificação de assinatura.


Limites de requisições

Estes endpoints compartilham o limite de requisições da API da sua organização com o restante da API v1 — veja Limites de requisições.