Ir para o conteúdo
Zoi ID

Construa com o Zoi ID

Duas APIs num mesmo host, clientes tipados em seis linguagens, um fluxo hospedado e um sandbox. O guia completo já está aberto para leitura, e as chaves de sandbox vêm com o acesso antecipado.

URL basehttps://kyc.zoi.network

Início rápido

  1. 1

    Crie uma sessão

    Seu backend chama POST /v1/partner/sessions com a sua chave de parceiro.

  2. 2

    Rode o fluxo

    Passe o token de cliente ao SDK ou abra verificationUrl num navegador.

  3. 3

    Receba o resultado

    Confira a assinatura do webhook e depois guarde o identificador da identidade e o seu status.

shell
curl https://kyc.zoi.network/v1/partner/sessions \
  -H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-user_42" \
  -d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'

Duas APIs

API de parceiros

De servidor para servidor, em /v1/partner. Envie a sua chave de parceiro como token Bearer. Nunca a coloque num app.

API de cliente

Usada pelo SDK, em /v1/client. Ela envia um token de cliente que vale para uma sessão por 60 minutos.

Convenções

  • Horários são milissegundos epoch, em campos cujo nome termina em At.
  • Datas são YYYY-MM-DD. Partes desconhecidas são XX, por exemplo 1990-XX-XX.
  • Requisições POST que criam ou decidem algo aceitam um cabeçalho Idempotency-Key. As chaves ficam guardadas por 24 horas.
  • Mudanças numa sessão enviam a revisão esperada. Se ela mudou, você recebe revision_conflict.

Fluxo de sessão

Seu backend cria a sessão. O SDK executa os passos. O servidor confere a ordem e responde step_not_allowed quando um passo chega cedo demais.

1. Crie uma sessão no seu backend

Chamar de novo para o mesmo usuário retoma a sessão aberta e dá um novo token de cliente.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "clientTokenExpiresAt": 1767225600000,
  "status": "created",
  "apiBaseUrl": "https://kyc.zoi.network",
  "resumed": false
}

2. Passos que o SDK executa

  1. 01

    Ler a sessão e os passos que ainda faltam.

    GET /v1/client/session

  2. 02

    Registrar o consentimento, com a escolha separada e opcional sobre a comparação facial.

    POST /v1/client/consent

  3. 03

    Conferir o app e o aparelho. O resultado faz parte da decisão.

    POST /v1/client/attestation/challenge, POST /v1/client/attestation

  4. 04

    Enviar o documento: ler o chip depois de escanear a MRZ, ou enviar fotos.

    POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos

  5. 05

    Gravar um vídeo curto com a câmera frontal enquanto o app mostra os movimentos de cabeça.

    POST /v1/client/liveness/challenge, POST /v1/client/liveness

  6. 06

    Enviar o país de residência, e o endereço no nível ampliado.

    POST /v1/client/profile

  7. 07

    Só com o requisito de endereço: enviar um documento e depois confirmar o endereço. Pulado quando o documento de identidade já o forneceu.

    POST /v1/client/address/document, POST /v1/client/address/confirm

  8. 08

    Só com um requisito de recursos: salvar as respostas, adicionar documentos quando a política pedir e então concluir a etapa.

    POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete

  9. 09

    Responder duas perguntas: pessoa politicamente exposta, e se age em nome próprio.

    POST /v1/client/declarations

  10. 10

    Enviar com a revisão atual.

    POST /v1/client/submit

O usuário também pode pedir que uma pessoa analise um resultado automático (POST /review-request) ou retirar o consentimento, o que cancela a sessão e apaga os arquivos na hora (POST /withdraw).

Comprovação de endereço e origem de recursos

Adicione requirements a POST /v1/partner/sessions para verificar mais do que a identidade. address pede comprovação de endereço. source_of_funds pergunta de onde vem o dinheiro, e source_of_wealth também pergunta o patrimônio total e como ele foi construído. Os dois valores de recursos juntos formam uma única etapa, e ambos se combinam com address.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "appId": "com.example.app",
  "requirements": ["address", "source_of_funds"],
  // Optional. Without them your partner default applies, else ours.
  "addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
  "fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}

Políticas opcionais

Sem política, vale o seu padrão de parceiro, ou então o nosso. A política é copiada para a sessão quando ela é criada.

  • addressPolicy: maxAgeDays (padrão 90, de 1 a 366), acceptedTypes (padrão: contas de consumo, de telefone e documentos bancários, documentos fiscais, cartas do governo e certificados de residência) e signedOnly (padrão false).
  • fundsPolicy: questions, evidence (none, optional ou required, padrão none), acceptedTypes, maxAgeDays (padrão 180), currency (ISO 4217, padrão USD) e brackets para as faixas.
  • Uma sessão aberta com outros requisitos que ainda não foi enviada é cancelada e uma nova começa, como numa mudança de nível.
  • Teste cada resultado com cenários de sandbox como address_signed_pdf, address_document_review, funds_signed_documents e funds_review.

Sessões de acompanhamento

Verifique só os requisitos de um usuário cuja identidade já está ativa com você. Envie reuseIdentity: true junto com requirements.

  • O usuário dá o consentimento e conclui só as etapas novas. Documento, selfie e declarações não são pedidos de novo.
  • Na aprovação, a identidade mantém o identificador, a data de verificação e a validade, e recebe o novo resultado. Você recebe session.approved e identity.updated.
  • Sem uma identidade ativa, a chamada responde 409 step_not_allowed.
  • Dispare a partir das suas próprias regras de risco, como um depósito alto, uma marcação de PEP ou um país de risco. Suas regras ficam no seu backend, num só lugar.
POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11

{
  "externalUserId": "user_42",
  "appId": "com.example.app",
  "requirements": ["source_of_funds", "source_of_wealth"],
  "reuseIdentity": true,
  "fundsPolicy": { "evidence": "required" }
}

// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }

Resultados e permissões

Sessões aprovadas trazem address e funds, e a identidade guarda o mais recente de cada um. Os campos de resumo vêm com qualquer permissão. Os detalhes exigem uma permissão.

  • Permissão address: as linhas do endereço. Sem ela, você recebe verified, method, evidenceLevel, documentType, documentDate, verifiedAt e country.
  • Permissão financial_data: answers, documents e findings. Sem ela, você recebe kinds, evidenceLevel, reviewed, documentCount e verifiedAt. Nunca faz parte de eligibility.
  • evidenceLevel é high, medium, low ou none. Métodos de endereço, do mais forte ao mais fraco: wallet, id_document, signed_pdf, official_code, document_review e declared.
  • Os documentos não são enviados pela API. Membros com o papel de compliance podem ver as páginas dos documentos de origem de recursos no painel quando o ambiente tem a permissão financial_data. Cada visualização é registrada.
GET /v1/partner/sessions/:id
"session": {
  "id": "ses_...",
  "status": "approved",
  "requirements": ["address", "source_of_funds"],
  "reusesIdentity": false,
  "address": {
    "verified": true,
    "method": "signed_pdf",
    "evidenceLevel": "high",
    "documentType": "bank_statement",
    "documentDate": "2026-10-14",
    "verifiedAt": 1767225600000,
    "country": "DE",
    // scope "address"
    "address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
  },
  "funds": {
    "kinds": ["source_of_funds"],
    "evidenceLevel": "medium",
    "reviewed": true,
    "documentCount": 2,
    "verifiedAt": 1767225600000,
    // scope "financial_data"
    "answers": {
      "employmentStatus": "employed",
      "income": { "min": 50000, "max": 100000, "currency": "EUR" },
      "sources": ["salary", "savings"],
      ...
    },
    "documents": [
      { "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
        "signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
      ...
    ],
    "findings": []
  }
}

Fluxo web

Sem SDK? Envie o usuário para a nossa página hospedada. Ela usa a mesma API, as mesmas verificações e a mesma fila de revisão.

1. Obtenha o link

POST /v1/partner/sessions retorna verificationUrl. Passe redirectUrl para mandar o usuário de volta no fim. O endereço deve começar com uma das suas origens de redirecionamento registradas.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app",
  "redirectUrl": "https://example.com/kyc/done"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
  ...
}
  • O token do cliente fica no fragmento da URL, depois de #. Os navegadores nunca enviam o fragmento a um servidor, então o token não aparece em logs nem no cabeçalho Referer. A página lê o token, remove da barra de endereço e guarda só na memória.
  • Adicione lang ao fragmento para escolher um dos 19 idiomas. Sem ele, a página usa o idioma do navegador.
  • Recarregar a página perde o token. Crie um novo link para a mesma sessão no seu backend.
  • A página envia channel: "web" junto com o consentimento. Navegadores não conseguem ler chips de documentos, então sessões web usam fotos do documento ou uma carteira de identidade digital. Elas pulam a verificação do aparelho, e fotos de documentos enviadas pela web são sempre decididas por um analista treinado.
  • Em um computador, a página mostra um código QR para o usuário continuar no celular.

2. Quando o usuário termina

Quando o fluxo termina (enviado, cancelado ou um status final), a página envia esta mensagem para cada um destes que existir: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid e window.opener. Depois abre redirectUrl com zoiIdSession e zoiIdStatus adicionados.

Nunca confie na mensagem nem nos parâmetros do redirecionamento. Confie só no seu webhook ou em GET /v1/partner/sessions/:id.

postMessage
// Sent to each bridge that exists:
//   window.ReactNativeWebView.postMessage(JSON string)
//   window.webkit.messageHandlers.zoiId.postMessage(object)
//   window.ZoiIdAndroid.postMessage(JSON string)
//   window.opener.postMessage(object, "<origin of your redirectUrl>")
//     (popups only, and only when the session has a redirectUrl)
{
  "source": "zoi-id",
  "type": "finished",        // or "cancelled"
  "sessionId": "ses_...",
  "status": "processing"     // the session status at that moment
}

// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing

3. Configuração da webview

Android WebView

Ative javaScriptEnabled e domStorageEnabled e defina mediaPlaybackRequiresUserGesture como false. Em um WebChromeClient, conceda onPermissionRequest para RESOURCE_VIDEO_CAPTURE depois que seu app tiver a permissão CAMERA. Adicione um seletor de arquivos para a alternativa com fotos.

Kotlin
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
  override fun onPermissionRequest(request: PermissionRequest) {
    // Only after your app holds Manifest.permission.CAMERA.
    if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
      request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
    } else {
      request.deny()
    }
  }
  override fun onShowFileChooser(
    view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
  ): Boolean {
    // Open a picker and pass the result to callback (photo fallback).
    return true
  }
}

iOS WKWebView

iOS 14.3 ou mais recente. Defina allowsInlineMediaPlayback como true e adicione NSCameraUsageDescription ao seu app. No iOS 15 ou mais recente, você pode decidir a permissão da câmera no delegate de UI.

Swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below

func webView(_ webView: WKWebView,
             requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo,
             type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
  decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescription

React Native WebView

Defina mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback e javaScriptEnabled. Seu app precisa da permissão de câmera.

TSX
<WebView
  source={{ uri: verificationUrl }}
  javaScriptEnabled
  allowsInlineMediaPlayback
  mediaCapturePermissionGrantType="grant"
  onMessage={(event) => {
    const message = JSON.parse(event.nativeEvent.data);
    if (message.source === "zoi-id") {
      // Close the webview, then ask your backend for the real status.
    }
  }}
/>

iframes não são compatíveis, por causa das permissões de câmera e de clickjacking. Use uma aba do navegador, um popup ou uma webview.

Status

Sessão

created
A sessão existe, nada foi enviado ainda.
in_progress
Consentimento dado, os passos estão sendo enviados.
processing
Enviada, as verificações estão rodando.
review
Aguardando um analista treinado.
approved
Final. A identidade está verificada.
rejected
Final. Veja os códigos de motivo.
resubmit
O usuário precisa refazer os passos em requiredSteps.
expired
Sem decisão final a tempo.
cancelled
Cancelada pelo parceiro, pelo usuário ou por uma exclusão.

Identidade

active
Utilizável.
suspended
Em espera, por exemplo após um novo candidato de sanções.
expired
O documento ou o período de verificação terminou. Uma nova sessão verifica de novo.
revoked
Final.

SDK

O SDK para React Native (Expo) dá a você um cliente de API tipado, leitura de chip por NFC e verificação do aparelho. Ele é headless, então seu app mantém controle total das próprias telas e da câmera. Nos seus servidores, use os clientes tipados para TypeScript, Python, Go, PHP, Java e Ruby.

@zoi/id-sdk
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";

// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });

const session = await client.getSession();
switch (nextStep(session)) {
  case "consent":   /* show the notice and the biometric choice */ break;
  case "document":  /* chip read or photos */ break;
  case "liveness":  /* record the video */ break;
  case "address":   /* upload a document, then confirm the address */ break;
  case "funds":     /* answers, then documents when the policy asks */ break;
  case "submit":    await client.submit(session.revision); break;
  case "done":      /* wait for your backend to get the webhook */ break;
}

Webhooks

Enviamos um webhook quando uma sessão ou identidade muda. Sempre confira a assinatura antes de confiar no conteúdo.

  • Todo webhook traz um cabeçalho Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 é o HMAC-SHA256 do texto t.raw body com o seu segredo de webhook. Use o corpo bruto, antes de ler o JSON.
  • Enquanto um segredo está sendo trocado, o cabeçalho traz um v1 por segredo ativo. Aceite o webhook se qualquer um deles bater.
  • Recuse se t estiver a mais de 300 segundos de agora.
  • Entregas com falha são repetidas por 72 horas. Cada nova tentativa é assinada de novo com um novo t.
  • Identidades trazem uma revision. Aplique um evento só se a revisão dele for mais nova que a que você guardou.
identity.suspended
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9

{
  "v": 1,
  "eventId": "evt_...",
  "type": "identity.suspended",
  "createdAt": 1767225600000,
  "object": "identity",
  "externalUserId": "user_42",
  "identity": {
    "identityId": "idn_...",
    "revision": 7,
    "status": "suspended",
    "level": "standard",
    "verifiedAt": 1764547200000,
    "validUntil": 1859155200000,
    "ageOver18": true,
    "captureMethod": "nfc"
  }
}

Tipos de evento

  • session.processing
  • session.review
  • session.approved
  • session.rejected
  • session.resubmit
  • session.expired
  • session.cancelled
  • identity.updated
  • identity.suspended
  • identity.reinstated
  • identity.expired
  • identity.revoked

Conferir a assinatura em TypeScript

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

const TOLERANCE_SECONDS = 300;

export function verifyZoiIdSignature(
  rawBody: string,
  header: string | null,
  secret: string,
  nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
  if (!header) return false;

  let t: string | undefined;
  const signatures: Buffer[] = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=");
    if (!value) continue;
    if (key === "t") t = value;
    if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
  }
  if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
  if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}

// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
  const rawBody = await request.text();
  const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
  if (!ok) return new Response("bad signature", { status: 400 });

  const event = JSON.parse(rawBody);
  // Store event.eventId to skip duplicates, then apply it if the revision is newer.
  return new Response(null, { status: 204 });
}

Conferir a assinatura em Go

verify.go
package zoiid

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

const toleranceSeconds = 300

// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
	var t string
	var sigs [][]byte
	for _, part := range strings.Split(header, ",") {
		key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
		if !ok || value == "" {
			continue
		}
		switch key {
		case "t":
			t = value
		case "v1":
			if b, err := hex.DecodeString(value); err == nil {
				sigs = append(sigs, b)
			}
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || len(sigs) == 0 {
		return false
	}
	if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(rawBody)
	expected := mac.Sum(nil)
	for _, sig := range sigs {
		if hmac.Equal(sig, expected) {
			return true
		}
	}
	return false
}

Feed de eventos

GET /v1/partner/events devolve todos os eventos dos últimos 30 dias, do mais antigo ao mais novo. Passe after com o último nextCursor para ler a próxima página. Use depois de uma queda longa, ou para conferir que você não perdeu nada.

GET /v1/partner/events
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>

200 OK
{
  "items": [ /* webhook payloads, oldest first */ ],
  "nextCursor": "..."   // null when there is nothing more
}

Reportes de fraude

Avise quando uma sessão decidida se revelar fraude, por exemplo depois de um chargeback. Os reportes alimentam a nossa medição de aprovações erradas. Eles não mudam a sessão, a identidade nem o resultado do usuário.

  • Permissão sessions:write. Só para sessões decididas (aprovadas ou rejeitadas). Antes de uma decisão, a chamada responde 409.
  • Um corpo vazio reporta confirmed_fraud com a categoria other. status também pode ser suspected_fraud, ou cleared para retirar o seu próprio reporte.
  • Categorias: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover e other. Sem categoria com cleared.
  • note é texto livre para os nossos analistas, com no máximo 1000 caracteres. Nunca coloque nomes, números de documentos ou outros dados pessoais nele.
  • Um reporte novo responde 201. O mesmo reporte de novo devolve o que já está guardado, com 200. Envie uma Idempotency-Key para repetir com segurança. No máximo 20 reportes por sessão.
  • O mesmo reporte está a um clique na página da sessão no painel.
POST /v1/partner/sessions/:id/fraud-reports
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812

{
  "status": "confirmed_fraud",
  "category": "impostor",
  "note": "Chargeback on the first deposit"
}

201 Created
{
  "id": "flb_...",
  "sessionId": "ses_...",
  "status": "confirmed_fraud",
  "category": "impostor",
  "noteKept": true,
  "createdAt": 1767225600000
}

Exclusão

DELETE /v1/partner/users/:externalUserId apaga os dados de um usuário quando ele encerra a conta ou pede a exclusão. A resposta lista o que foi apagado agora, e o que é guardado, por quê e até quando.

DELETE /v1/partner/users/:externalUserId
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>

200 OK
{
  "deleted": ["images", "chip_files", "video", "personal_data", "address"],
  "retained": [
    { "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
    { "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
  ],
  "backupsPurgedWithinDays": 30
}

Erros

Todo erro tem o mesmo formato e um status HTTP fixo por código.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
CódigoHTTPSignificado
bad_request400A requisição não segue o contrato.
unauthorized401Chave ou token ausente ou errado.
token_expired401O token de cliente venceu. Crie um novo no seu backend.
forbidden403Esta chave não pode fazer isso.
not_found404Essa sessão ou esse usuário não existe.
session_closed409A sessão é final. Nada mais pode ser enviado.
step_not_allowed409Ordem errada, ou o passo não é necessário agora.
revision_conflict409A revisão da sessão mudou. Leia de novo.
idempotency_conflict409A mesma Idempotency-Key foi usada com outra requisição.
challenge_expired410O desafio venceu ou já foi usado. Peça um novo.
payload_too_large413O envio é grande demais.
unsupported_media_type415Esse tipo de arquivo não é aceito.
rate_limited429Requisições demais. Tente de novo mais tarde.
internal500Algo deu errado do nosso lado.
dependency_unavailable503Um serviço de que precisamos está fora do ar. Tente de novo mais tarde.

Códigos de motivo

As sessões trazem reasonCodes quando precisam de reenvio ou foram recusadas. Mostre a sua própria mensagem traduzida para cada código. Nunca mostre o código bruto aos usuários.

  • consent_missing
  • consent_withdrawn
  • attestation_failed
  • document_expired
  • document_unreadable
  • document_not_supported
  • document_suspected_tampering
  • document_number_visible
  • chip_verification_failed
  • chip_proof_missing
  • chip_data_mismatch
  • mrz_invalid
  • photo_quality
  • face_not_found
  • face_mismatch
  • liveness_failed
  • underage
  • location_not_supported
  • residence_not_supported
  • sanctions_match
  • pep_review
  • duplicate_identity
  • possible_duplicate
  • declaration_requires_review
  • user_requested_review
  • reviewer_other
  • address_unreadable
  • address_document_too_old
  • address_document_not_accepted
  • address_name_mismatch
  • address_signature_required
  • funds_document_unreadable
  • funds_document_too_old
  • funds_document_not_accepted
  • funds_more_evidence
  • funds_evidence_rejected