Inyo

Primeros Pasos

Esta guía te acompaña a través de una verificación de identidad completa en el sandbox de Inyo. Al finalizar habrás creado una sesión, verificado un documento y una selfie a través del widget alojado, y recibido un resultado firmado en tu propio endpoint.


Requisitos previos

  • Credenciales de sandbox emitidas por Inyo durante el onboarding:
    • client_id y client_secret para el flujo client-credentials
    • Tu URL base de sandbox, y la confirmación de que tus rangos de IP de salida están en la lista de permitidos
    • Un webhookSecret — usado para verificar la firma de los resultados entregados
  • Un endpoint de webhook, accesible públicamente por HTTPS, registrado con Inyo como tu webhookUrl. Durante el desarrollo funciona un túnel (ngrok, Cloudflare Tunnel); alternativamente usa la entrega por redirección y omite el webhook por completo.
  • Un teléfono con cámara para el widget. El acceso a la cámara requiere un contexto seguro, así que abre el widget por HTTPS — los navegadores de escritorio también funcionan.
  • Un cliente REST (cURL, Postman o similar).

¿Aún no tienes credenciales? Ponte en contacto con nuestro equipo de ventas para solicitar acceso al sandbox.


Paso 1: Obtén un Token de Acceso

curl --request POST \
  --url https://{FQDN}/oauth/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data grant_type=client_credentials \
  --data client_id=$KYC_CLIENT_ID \
  --data client_secret=$KYC_CLIENT_SECRET
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "sessions verifications"
}

Guarda access_token y reutilízalo hasta que expire — consulta Autenticación para el manejo de renovación.


Paso 2: Crea una Sesión de Verificación

userRef es tu propio identificador de la persona que se está verificando. Se devuelve en cada resultado, así que usa el identificador con el que tu sistema ya referencia a sus usuarios.

curl --request POST \
  --url https://{FQDN}/v1/sessions \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{
  "userRef": "user-123",
  "language": "en",
  "delivery": { "mode": "webhook" }
}'
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "status": "pending",
  "widgetUrl": "https://{FQDN}/verify/8Kd2mQ…"
}

Persiste ahora el sessionId asociado a tu usuario — así es como correlacionarás el resultado entrante.

delivery.mode es webhook por defecto. Si no tienes un webhookUrl configurado, esta llamada devuelve 422; usa {"mode": "redirect", "redirectUrl": "https://you.example/kyc-done"} en su lugar.


Paso 3: Envía al Cliente al Widget

Abre widgetUrl en el navegador del cliente o en un webview nativo, o entrégalo por SMS o correo electrónico. El enlace lleva un código de un solo uso y es válido por 48 horas.

El cliente selecciona un tipo de documento, fotografía el documento, se toma una selfie y ve el resultado. No escribes ningún código de cámara — consulta Entrega del Widget para detalles de webview y personalización de marca.


Paso 4: Recibe el Resultado Firmado

Cuando la verificación termina, Inyo envía un POST con el resultado a tu webhookUrl con un encabezado X-Inyo-Signature:

POST /kyc-result HTTP/1.1
Content-Type: application/json
X-Inyo-Signature: t=1704829200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "userRef": "user-123",
  "status": "approved",
  "autoStatus": "approved",
  "document": {
    "type": "passport",
    "number": "A12345678",
    "issuingState": "USA",
    "issuingCountry": "USA",
    "expirationDate": "2033-09-30"
  },
  "person": {
    "firstName": "ALEX",
    "lastName": "MORGAN",
    "dateOfBirth": "1988-03-04",
    "nationality": "USA"
  },
  "checks": [
    { "name": "document_readable", "status": "passed", "group": "document", "detail": "all core fields extracted" },
    { "name": "document_mrz_valid", "status": "passed", "group": "document", "detail": "all check digits valid" },
    { "name": "document_not_expired", "status": "passed", "group": "document", "detail": "expirationDate 2033-09-30" },
    { "name": "face_match", "status": "passed", "group": "selfie", "detail": "CompareFaces similarity vs threshold 90.0" }
  ],
  "prefillMismatches": [],
  "provider": "inyo"
}

Verifica la firma antes de confiar en el payload. El encabezado es una lista separada por comas: t es el timestamp Unix en que se calculó la firma, y v1 es un HMAC-SHA256 con tu webhookSecret como clave sobre los bytes "<t>." + <cuerpo crudo>.

import crypto from "node:crypto";

const parts = new Map(
  (req.headers["x-inyo-signature"] ?? "")
    .split(",")
    .map((entry) => entry.trim().split("=")),
);

const timestamp = parts.get("t");
const signature = parts.get("v1");

const expected = crypto
  .createHmac("sha256", process.env.KYC_WEBHOOK_SECRET)
  .update(`${timestamp}.`)
  .update(rawBody)                       // bytes crudos, antes de JSON.parse
  .digest("hex");

const valid =
  timestamp &&
  signature &&
  Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300 &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

Dos cosas que este ejemplo abreviado omite y que el código de producción necesita: rechazar un encabezado que repita una versión, y comparar las longitudes antes de timingSafeEqual (lanza una excepción si no coinciden). Parsear el cuerpo y volver a serializarlo cambia los espacios en blanco y el orden de las claves y romperá la firma. Los detalles completos, incluidos el modo de redirección y la rotación, están en Recepción de Resultados.

Responde 2xx para confirmar la recepción. Inyo reintenta hasta tres veces con backoff, y luego se detiene.


Paso 5: Confirma del Lado del Servidor

GET /v1/sessions/{sessionId} es el registro autoritativo. Úsalo para conciliar, para recuperar un webhook perdido, o siempre que necesites certeza en lugar de una notificación push:

curl --request GET \
  --url https://{FQDN}/v1/sessions/$SESSION_ID \
  --header "Authorization: Bearer $ACCESS_TOKEN"
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "userRef": "user-123",
  "status": "approved",
  "step": "done",
  "deliveryMode": "webhook",
  "result": { "…": "the same normalized result" },
  "createdAt": "2026-07-31T14:02:11.481Z",
  "updatedAt": "2026-07-31T14:04:57.902Z"
}

Maneja Estos Tres Casos Antes de Salir a Producción

CasoPor qué importaDónde leer
status es in_reviewNo es una respuesta final — un humano decide, y se te notifica después. Puede llegar por webhook y en una redirección.Revisión Manual
Dos resultados para una sesiónUna decisión de analista o una reversión de control de calidad vuelve a entregar el resultado. Aplica el notifiedAt más alto, no la última llegada.Recepción de Resultados
Un check falló pero quieres el detallestatus es el resultado; los umbrales por tenant se citan en el detail de cada check.Checks y Decisiones

Próximos Pasos

PáginaQué cubre
Sesiones de VerificaciónPrefill, bloqueo del tipo de documento, verificaciones de datos, límites de captura
Entrega del WidgetIncrustación en webview, personalización de marca, localización
Verificación Servidor a ServidorUsar tu propia interfaz de captura en lugar del widget
Sandbox y Datos de PruebaCómo provocar resultados approved, declined e in_review