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_idyclient_secretpara 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.modeeswebhookpor defecto. Si no tienes unwebhookUrlconfigurado, esta llamada devuelve422; 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
| Caso | Por qué importa | Dónde leer |
|---|---|---|
status es in_review | No 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ón | Una 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 detalle | status es el resultado; los umbrales por tenant se citan en el detail de cada check. | Checks y Decisiones |
Próximos Pasos
| Página | Qué cubre |
|---|---|
| Sesiones de Verificación | Prefill, bloqueo del tipo de documento, verificaciones de datos, límites de captura |
| Entrega del Widget | Incrustación en webview, personalización de marca, localización |
| Verificación Servidor a Servidor | Usar tu propia interfaz de captura en lugar del widget |
| Sandbox y Datos de Prueba | Cómo provocar resultados approved, declined e in_review |
