Saltar al contenido
Zoi ID

Construye con Zoi ID

Dos API en un mismo host, clientes tipados en seis lenguajes, un flujo alojado y un sandbox. La guía completa ya se puede leer, y las claves de sandbox llegan con el acceso anticipado.

URL basehttps://kyc.zoi.network

Inicio rápido

  1. 1

    Cree una sesión

    Su backend llama a POST /v1/partner/sessions con su clave de socio.

  2. 2

    Ejecute el flujo

    Pase el token de cliente al SDK, o abra verificationUrl en un navegador.

  3. 3

    Reciba el resultado

    Compruebe la firma del webhook y luego guarde el identificador de identidad y su estado.

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

Dos API

API de socios

De servidor a servidor, bajo /v1/partner. Envía tu clave de socio como token Bearer. Nunca la pongas en una app.

API de cliente

La usa el SDK, bajo /v1/client. Envía un token de cliente que vale para una sesión durante 60 minutos.

Convenciones

  • Las horas son milisegundos epoch, en campos cuyo nombre termina en At.
  • Las fechas son YYYY-MM-DD. Las partes desconocidas son XX, por ejemplo 1990-XX-XX.
  • Las peticiones POST que crean o deciden algo aceptan un encabezado Idempotency-Key. Las claves se guardan 24 horas.
  • Los cambios en una sesión envían la revisión que esperan. Si cambió, recibes revision_conflict.

Flujo de sesión

Tu backend crea la sesión. El SDK ejecuta los pasos. El servidor comprueba el orden y responde step_not_allowed cuando un paso llega antes de tiempo.

1. Crea una sesión desde tu backend

Si la llamas otra vez para el mismo usuario, se reanuda la sesión abierta y se da un nuevo 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. Pasos que ejecuta el SDK

  1. 01

    Leer la sesión y los pasos que aún faltan.

    GET /v1/client/session

  2. 02

    Registrar el consentimiento, con la elección aparte y opcional sobre la comparación facial.

    POST /v1/client/consent

  3. 03

    Revisar la app y el dispositivo. El resultado forma parte de la decisión.

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

  4. 04

    Enviar el documento: leer el chip tras escanear la MRZ, o 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

    Grabar un video corto con la cámara frontal mientras la app muestra los movimientos de cabeza.

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

  6. 06

    Enviar el país de residencia, y la dirección en el nivel ampliado.

    POST /v1/client/profile

  7. 07

    Solo con el requisito de dirección: subir un documento y luego confirmar la dirección. Se omite cuando el documento de identidad ya la aportó.

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

  8. 08

    Solo con un requisito de fondos: guardar las respuestas, añadir documentos si la política los pide y luego completar el paso.

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

  9. 09

    Responder dos preguntas: persona expuesta políticamente, y si actúa en nombre propio.

    POST /v1/client/declarations

  10. 10

    Enviar con la revisión actual.

    POST /v1/client/submit

El usuario también puede pedir que una persona revise un resultado automático (POST /review-request) o retirar su consentimiento, lo que cancela la sesión y borra sus archivos al instante (POST /withdraw).

Comprobante de domicilio y origen de los fondos

Añade requirements a POST /v1/partner/sessions para verificar algo más que la identidad. address pide un comprobante de domicilio. source_of_funds pregunta de dónde viene el dinero, y source_of_wealth también pregunta por el patrimonio total y cómo se formó. Los dos valores de fondos juntos son un solo paso, y ambos se combinan con 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 opcionales

Sin política, se aplica tu valor por defecto de socio y, si no lo hay, el nuestro. La política se copia en la sesión al crearla.

  • addressPolicy: maxAgeDays (por defecto 90, de 1 a 366), acceptedTypes (por defecto: documentos de servicios, telefonía y banco, documentos fiscales, cartas oficiales y certificados de residencia) y signedOnly (por defecto false).
  • fundsPolicy: questions, evidence (none, optional o required, por defecto none), acceptedTypes, maxAgeDays (por defecto 180), currency (ISO 4217, por defecto USD) y brackets para los rangos.
  • Una sesión abierta con otros requisitos que aún no se envió se cancela y empieza una nueva, igual que con un cambio de nivel.
  • Prueba cada resultado con escenarios de sandbox como address_signed_pdf, address_document_review, funds_signed_documents y funds_review.

Sesiones de seguimiento

Verifica solo los requisitos de un usuario cuya identidad ya está activa contigo. Envía reuseIdentity: true junto con requirements.

  • El usuario da su consentimiento y completa solo los pasos nuevos. No se vuelve a pedir documento, selfie ni declaraciones.
  • Al aprobarse, la identidad conserva su identificador, su fecha de verificación y su validez, y recibe el nuevo resultado. Recibes session.approved y identity.updated.
  • Sin una identidad activa, la llamada responde 409 step_not_allowed.
  • Iníciala desde tus propias reglas de riesgo, como un depósito grande, una alerta PEP o un país de riesgo. Tus reglas se quedan en tu backend, en un solo 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 y permisos

Las sesiones aprobadas llevan address y funds, y la identidad conserva el más reciente de cada uno. Los campos de resumen llegan con cualquier permiso. Los detalles necesitan un permiso.

  • Permiso address: las líneas de la dirección. Sin él recibes verified, method, evidenceLevel, documentType, documentDate, verifiedAt y country.
  • Permiso financial_data: answers, documents y findings. Sin él recibes kinds, evidenceLevel, reviewed, documentCount y verifiedAt. Nunca forma parte de eligibility.
  • evidenceLevel es high, medium, low o none. Métodos de dirección, del más sólido al más débil: wallet, id_document, signed_pdf, official_code, document_review y declared.
  • Los documentos no se envían por la API. Los miembros con el rol de cumplimiento pueden ver las páginas de los documentos de origen de los fondos en el panel cuando el entorno tiene el permiso financial_data. Cada consulta queda 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": []
  }
}

Flujo web

¿Sin SDK? Envía al usuario a nuestra página alojada. Usa la misma API, las mismas comprobaciones y la misma cola de revisión.

1. Obtén el enlace

POST /v1/partner/sessions devuelve verificationUrl. Pasa redirectUrl para que el usuario vuelva contigo al terminar. Debe empezar por uno de tus orígenes de redirección registrados.

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",
  ...
}
  • El token de cliente va en el fragmento de la URL, después de #. Los navegadores nunca envían el fragmento a un servidor, así que el token no queda en registros ni en la cabecera Referer. La página lo lee, lo quita de la barra de direcciones y solo lo guarda en memoria.
  • Añade lang al fragmento para elegir uno de los 19 idiomas. Sin él, la página usa el idioma del navegador.
  • Al recargar se pierde el token. Crea un enlace nuevo para la misma sesión desde tu backend.
  • La página envía channel: "web" con el consentimiento. Los navegadores no pueden leer chips de documentos, así que las sesiones web usan fotos del documento o un monedero de identidad digital. Se saltan la comprobación del dispositivo, y las fotos de documentos enviadas desde la web siempre las decide un revisor capacitado.
  • En un ordenador, la página muestra un código QR para que el usuario siga en un teléfono.

2. Cuando el usuario termina

Cuando el flujo termina (enviado, cancelado o un estado final), la página envía este mensaje a cada uno de estos que exista: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid y window.opener. Después abre redirectUrl con zoiIdSession y zoiIdStatus añadidos.

Nunca confíes en el mensaje ni en los parámetros de la redirección. Confía solo en tu webhook o en 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. Ajustes de la webview

Android WebView

Activa javaScriptEnabled y domStorageEnabled, y pon mediaPlaybackRequiresUserGesture en false. En un WebChromeClient, concede onPermissionRequest para RESOURCE_VIDEO_CAPTURE cuando tu app ya tenga el permiso CAMERA. Añade un selector de archivos para la alternativa con 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 o posterior. Pon allowsInlineMediaPlayback en true y añade NSCameraUsageDescription a tu app. Desde iOS 15 puedes decidir el permiso de cámara en el delegado 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

Define mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback y javaScriptEnabled. Tu app necesita el permiso de cámara.

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

Los iframes no son compatibles, por los permisos de cámara y por el clickjacking. Usa una pestaña del navegador, una ventana emergente o una webview.

Estados

Sesión

created
La sesión existe, aún no se subió nada.
in_progress
Consentimiento dado, se están subiendo los pasos.
processing
Enviada, las revisiones están en curso.
review
Esperando a un revisor capacitado.
approved
Final. La identidad está verificada.
rejected
Final. Mira los códigos de motivo.
resubmit
El usuario debe repetir los pasos de requiredSteps.
expired
No hubo decisión final a tiempo.
cancelled
Cancelada por el socio, el usuario o un borrado.

Identidad

active
Utilizable.
suspended
En pausa, por ejemplo tras un nuevo candidato de sanciones.
expired
El documento o el periodo de verificación terminó. Una nueva sesión vuelve a verificar.
revoked
Final.

SDK

El SDK para React Native (Expo) te da un cliente de API tipado, lectura de chips por NFC y comprobaciones del dispositivo. No incluye interfaz, así que tu app mantiene el control total de sus pantallas y de su cámara. Para tus servidores, usa los clientes tipados para TypeScript, Python, Go, PHP, Java y 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 un webhook cuando cambia una sesión o una identidad. Comprueba siempre la firma antes de confiar en el contenido.

  • Cada webhook lleva un encabezado Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 es el HMAC-SHA256 del texto t.raw body con tu secreto de webhook. Usa el cuerpo sin procesar, antes de leer el JSON.
  • Mientras se cambia un secreto, el encabezado lleva un v1 por cada secreto activo. Acepta el webhook si alguno coincide.
  • Recházalo si t está a más de 300 segundos de ahora.
  • Los envíos fallidos se reintentan durante 72 horas. Cada reintento se firma de nuevo con un t nuevo.
  • Las identidades llevan una revision. Aplica un evento solo si su revisión es más nueva que la que guardaste.
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

Comprobar la firma en 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 });
}

Comprobar la firma en 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 devuelve todos los eventos de los últimos 30 días, del más antiguo al más nuevo. Pasa after con el último nextCursor para leer la página siguiente. Úsalo tras una caída larga o para comprobar que no te falta 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

Avísanos cuando una sesión ya decidida resulte ser un fraude, por ejemplo tras un contracargo. Los reportes alimentan nuestra medición de aprobaciones erróneas. No cambian la sesión, la identidad ni el resultado del usuario.

  • Permiso sessions:write. Solo para sesiones decididas (aprobadas o rechazadas). Antes de una decisión, la llamada responde 409.
  • Un cuerpo vacío reporta confirmed_fraud con la categoría other. status también puede ser suspected_fraud, o cleared para retirar tu propio reporte.
  • Categorías: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover y other. Sin categoría con cleared.
  • note es texto libre para nuestros analistas, de 1000 caracteres como máximo. Nunca pongas nombres, números de documentos ni otros datos personales.
  • Un reporte nuevo responde 201. El mismo reporte otra vez devuelve el ya guardado con 200. Envía una Idempotency-Key para reintentar sin riesgo. Como máximo 20 reportes por sesión.
  • El mismo reporte está a un clic, en la página de la sesión del panel.
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
}

Borrado

DELETE /v1/partner/users/:externalUserId borra los datos de un usuario cuando cierra su cuenta o pide el borrado. La respuesta indica qué se borró ahora y qué se guarda, por qué y hasta cuándo.

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
}

Errores

Todos los errores tienen la misma forma y un estado HTTP fijo por código.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
CódigoHTTPSignificado
bad_request400La petición no cumple el contrato.
unauthorized401Falta la clave o el token, o es incorrecto.
token_expired401El token de cliente caducó. Crea uno nuevo desde tu backend.
forbidden403Esta clave no puede hacer esto.
not_found404No existe esa sesión o ese usuario.
session_closed409La sesión es final. No se admiten más subidas.
step_not_allowed409Orden incorrecto, o el paso no hace falta ahora.
revision_conflict409La revisión de la sesión cambió. Léela de nuevo.
idempotency_conflict409La misma Idempotency-Key se usó con otra petición.
challenge_expired410El desafío caducó o ya se usó. Pide uno nuevo.
payload_too_large413El archivo es demasiado grande.
unsupported_media_type415No se acepta este tipo de archivo.
rate_limited429Demasiadas peticiones. Inténtalo más tarde.
internal500Algo salió mal de nuestro lado.
dependency_unavailable503Un servicio que necesitamos no funciona. Inténtalo más tarde.

Códigos de motivo

Las sesiones llevan reasonCodes cuando hay que reenviar algo o fueron rechazadas. Muestra tu propio mensaje traducido para cada código. Nunca muestres el código tal cual a los usuarios.

  • 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