PIN-RUT Verificación de Identidad
El servicio PIN-RUT permite verificar la identidad de personas mediante un PIN numérico personal vinculado a su RUT chileno. La persona crea su PIN una sola vez y lo usa para autorizar operaciones solicitadas por tu sistema.
Casos de uso típicos:
- Firma de contratos o documentos
- Autorización de transacciones financieras
- Confirmación de identidad en procesos de onboarding
- Validación de operaciones sensibles
Requisitos Previos
Sección titulada «Requisitos Previos»Antes de usar la API necesitas:
- Una API Key con rol
FULL-API - El tenant debe tener el servicio PIN_RUT habilitado
- Una integración PIN-RUT configurada con las URLs permitidas
Configuración de Integraciones
Sección titulada «Configuración de Integraciones»Antes de crear transacciones, debes configurar una integración desde el panel de administración:
Configuración → PIN-RUT → Integraciones
Campos de la Integración
Sección titulada «Campos de la Integración»| Campo | Descripción |
|---|---|
| Slug | Identificador único inmutable (ej: mi_app_prod). Se usa en cada request. |
| Nombre | Nombre descriptivo que la persona verá en la pantalla de verificación |
| URIs de redirect | URLs HTTPS donde el browser redirige tras completar la verificación |
| URLs de callback | URLs HTTPS donde se envían webhooks con el resultado |
| Orígenes iframe | URLs HTTPS autorizadas para embeber la pantalla PIN en un iframe |
| URI de redirect default | Redirect por defecto si no se especifica en cada transacción |
Ejemplo de Integración
Sección titulada «Ejemplo de Integración»| Campo | Valor |
|---|---|
| Slug | mi_app_prod |
| Nombre | Mi Aplicación |
| Redirect URIs | https://miapp.cl/pin-callback |
| Callback URLs | https://miapp.cl/webhooks/pin |
| Redirect default | https://miapp.cl/pin-callback |
Flujo de Verificación
Sección titulada «Flujo de Verificación»Tu Sistema Redcumbre Persona │ │ │ │── POST /pin/transactions ──▶│ │ │ (Bearer token + body) │ │ │ │ │ │◀── authorize_url ──────────│ │ │ (+ transaction_id) │ │ │ │ │ │── Redirige browser ────────▶│ │ │ a authorize_url │ │ │ │── Pantalla de autorización ▶│ │ │ │ │ │◀── Se autentica: PIN o cara│ │ │ │ │◀── Webhook (callback_url) ─│ │ │ pin.transaction.authorized │ │ │ │ │ │── Redirect a redirect_uri ▶│ │ │ ?transaction_id=X │ │ │ &status=authorized │ │ │ &state=Y │ │ │ │ │── GET /result ─────────────▶│ │ │ (verificar estado final) │ │ │◀── { status: authorized } ─│ │Pasos:
- Tu sistema crea una transacción con los datos de la operación y la modalidad con que la persona autoriza
- Rediriges al usuario a la
authorize_urlretornada - La persona ve los datos de la operación y se autentica con la modalidad que pediste
- Tu sistema recibe el resultado vía webhook y/o redirect
- Opcionalmente, consultas el resultado vía API
Las dos modalidades
Sección titulada «Las dos modalidades»authentication_method | La persona autoriza con | Cuándo usarla |
|---|---|---|
pin | Su PIN de 6 dígitos. Si todavía no tiene PIN, lo crea después de autenticarse con su cara, y crearlo autoriza la operación | Operaciones frecuentes, donde el PIN basta |
face | Una prueba de vida contra el reconocimiento facial de su identidad acreditada. El PIN no interviene: no se pide, y un PIN bloqueado no la impide | Operaciones donde necesitas presencia de la persona, no sólo conocimiento |
En las dos, si la persona no tiene su identidad acreditada, la plataforma la guía primero a acreditarla —cédula por ambos lados y prueba de vida— y después sigue con la modalidad que pediste. No haces nada distinto: es la misma URL.
Crear Transacción
Sección titulada «Crear Transacción»Endpoint
Sección titulada «Endpoint»POST /{tenantSlug}/pin/transactionsHeaders
Sección titulada «Headers»Authorization: Bearer {api_key}Content-Type: application/json{ "integration_slug": "mi_app_prod", "rut": "12.345.678-9", "authentication_method": "pin", "operation_type": "firma_contrato", "operation_label": "Firmar Contrato de Servicios N° 2026-042", "operation_detail": "Contrato por 12 meses", "redirect_uri": "https://miapp.cl/pin-callback", "callback_url": "https://miapp.cl/webhooks/pin", "state": "session-abc-123"}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
integration_slug | string | Sí | Slug de la integración configurada |
rut | string | Sí | RUT chileno de la persona (ej: 12.345.678-9 o 12345678-9) |
authentication_method | string | Sí | Con qué autoriza la persona: pin o face. No tiene valor por defecto: sin él, 400 |
operation_type | string | Sí | Tipo de operación en snake_case (máx. 50 chars) |
operation_label | string | Sí | Descripción visible para la persona (máx. 200 chars) |
operation_detail | string | No | Describe la operación que la persona va a autorizar. No incluyas datos personales de otras personas ni datos sensibles, como información de salud. Se conserva como evidencia de la autorización y se anonimiza junto con ella. |
redirect_uri | string | No | URL HTTPS de redirect. Debe estar en las URIs permitidas de la integración |
callback_url | string | No | URL HTTPS para webhook. Debe estar en las URLs permitidas de la integración |
state | string | No | Valor opaco devuelto sin modificar en redirect y webhook (máx. 2048 chars) |
Ejemplo con curl
Sección titulada «Ejemplo con curl»curl -X POST "https://api.redcumbre.cl/{tenantSlug}/pin/transactions" \ -H "Authorization: Bearer {api_key}" \ -H "Content-Type: application/json" \ -d '{ "integration_slug": "mi_app_prod", "rut": "12.345.678-9", "authentication_method": "face", "operation_type": "firma_contrato", "operation_label": "Firmar Contrato N° 2026-042", "redirect_uri": "https://miapp.cl/pin-callback", "callback_url": "https://miapp.cl/webhooks/pin", "state": "session-abc-123" }'Respuesta Exitosa (201)
Sección titulada «Respuesta Exitosa (201)»{ "success": true, "data": { "transaction_id": "cedd740a-e5a3-43a2-9896-5ac45dfdb4dc", "authorize_url": "/p/pin/authorize/7f3b9a61-2c4e-4d8f-b1a0-5e6d7c8b9a0f", "expires_at": "2026-05-18T23:30:04.594Z", "operation_hash": "06860b21052299ad..." }}| Campo | Tipo | Descripción |
|---|---|---|
transaction_id | string (UUID) | Identificador único de la transacción |
authorize_url | string | Ruta relativa para redirigir a la persona. Prefijar con la URL base de la plataforma. La ruta contiene un identificador propio del enlace, distinto de transaction_id: úsala tal cual, sin extraer ni construir ese identificador |
expires_at | string (ISO 8601) | Límite absoluto de la transacción: la creación más 7 días y 48 horas, igual para toda transacción. Puede vencer antes (ver expiration_reason) |
operation_hash | string | Hash SHA-256 de la operación (para verificación de integridad) |
Redirect de la Persona
Sección titulada «Redirect de la Persona»Construye la URL completa y redirige al usuario:
https://app.redcumbre.cl{authorize_url}Por ejemplo:
https://app.redcumbre.cl/p/pin/authorize/7f3b9a61-2c4e-4d8f-b1a0-5e6d7c8b9a0fQué pasa cuando la persona abre el enlace
Sección titulada «Qué pasa cuando la persona abre el enlace»La pantalla le muestra primero la operación y quién la pide, y después la lleva por lo que le falte:
- Con
piny PIN: ingresa su PIN. - Con
face, o conpiny sin PIN: se autentica con su cara. Conpin, después crea su PIN, y crearlo autoriza. - Sin identidad acreditada, en las dos modalidades: la acredita primero —consentimiento, cédula por ambos lados y prueba de vida— y vuelve al mismo punto.
Los plazos cortos corren desde que la persona abre el enlace, no desde que creas la transacción: 5 minutos para
ingresar el PIN y 30 minutos para la prueba de vida. La acreditación de identidad tiene el plazo de su sesión: hasta la
creación más 7 días y 48 horas. Si nadie abre el enlace en 7 días, la transacción vence con not_started.
Consultar Resultado
Sección titulada «Consultar Resultado»Endpoint
Sección titulada «Endpoint»GET /{tenantSlug}/pin/transactions/{transaction_id}/resultHeaders
Sección titulada «Headers»Authorization: Bearer {api_key}Respuesta
Sección titulada «Respuesta»{ "success": true, "data": { "transaction_id": "cedd740a-e5a3-43a2-9896-5ac45dfdb4dc", "status": "authorized", "integration_slug": "mi_app_prod", "rut": "12.345.678-9", "operation_type": "firma_contrato", "operation_label": "Firmar Contrato N° 2026-042", "authentication_method": "face", "created_at": "2026-05-09T23:30:04.594Z", "authorized_at": "2026-05-09T23:33:01.538Z", "expires_at": "2026-05-18T23:30:04.594Z", "state": "session-abc-123", "expiration_reason": null, "receipt": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ii4uLiJ9.eyJ0cmFuc2FjY2lvbklkIjoiLi4uIn0.MEUCIQ...", "enrollment_charge": { "available": true, "price": 350, "currency": "CLP", "includesIva": false, "note": "Valores netos + IVA. Precio de lista del plan vigente, no una liquidación: la factura manda.", "desglose": [ { "metrica": "kyc_enrolamiento_iniciado", "price": 150 }, { "metrica": "kyc_enrolamiento_logrado", "price": 200 } ] }, "face_authentication_charge": { "available": false, "includesIva": false, "message": "El plan actual no tiene una tarifa unitaria resoluble para la autenticación facial. El cargo se aplica igual y el monto lo determina la factura." } }}El comprobante
Sección titulada «El comprobante»receipt es el comprobante de la autorización: un JWS compacto que firma la transacción, el hash de la operación,
la modalidad, el instante de la autorización y la verificación que la respalda. Llega sólo con la transacción
authorized; en cualquier otro estado la clave no existe.
Se verifica sin relación con nosotros, contra las claves públicas de
/.well-known/kyc-jwks.json: toma el kid del encabezado del
JWS, busca esa clave en el JWKS y valida la firma con cualquier biblioteca JOSE. Guárdalo junto a tu operación: es la
prueba de que la persona la autorizó.
El cargo de la acreditación de identidad
Sección titulada «El cargo de la acreditación de identidad»enrollment_charge informa el cargo de la acreditación de identidad que esta transacción gatilló.
- Es
nullcuando esa persona ya estaba acreditada y no hubo acreditación.nullsignifica no hubo cargo, no el cargo fue cero. - Cuando la tarifa del plan no se puede resolver, llega con
"available": falsey sumessage, sin monto. Nunca vas a recibir un cero por un cargo que sí ocurre. - El cargo de la transacción no se informa acá: está asumido en tu contrato y no cambia. El de la acreditación aparece a veces sí y a veces no —depende de si esa persona ya estaba acreditada—, y por eso es el único que te informamos por transacción.
El cargo de la autenticación facial
Sección titulada «El cargo de la autenticación facial»face_authentication_charge informa el cargo de la autenticación facial de esta transacción, con la misma forma que
enrollment_charge: las métricas pin_validacion_facial_iniciada y pin_validacion_facial_lograda que devengó, con su
precio de lista.
- Es
nullcuando la transacción no tuvo autenticación facial: la autorizó el PIN, o la acreditación de identidad. - Sin tarifa unitaria resoluble en tu plan llega con
"available": falsey sumessage, sin monto. Nunca un cero.
Las dos advertencias del monto son las mismas que para la acreditación: es precio de lista del plan vigente, no una liquidación —la factura manda—, y supone una tarifa unitaria plana.
El motivo del vencimiento
Sección titulada «El motivo del vencimiento»expiration_reason dice por qué venció una transacción:
| Valor | Cuándo |
|---|---|
"not_started" | La persona no empezó: nadie abrió el enlace en 7 días, o abrió una acreditación de identidad y no aceptó el consentimiento |
"not_completed" | La persona empezó y no terminó a tiempo: abrió el enlace y no ingresó el PIN, no completó la prueba de vida o no terminó la acreditación |
null | La transacción todavía no venció |
No existe un tercer valor: expiration_reason no te dice cómo terminó la acreditación ni la prueba de vida
—aprobada, rechazada o en revisión—, sólo si la persona empezó.
Cuando los datos de la transacción ya se anonimizaron
Sección titulada «Cuando los datos de la transacción ya se anonimizaron»La plataforma no conserva los datos personales de una transacción para siempre. Pasado su plazo, los anonimiza y
conserva el hecho: la transacción, su estado, sus fechas y operation_hash siguen respondiendo.
| Transacción | Cuándo | Qué cambia en la consulta |
|---|---|---|
No autorizada (expired, failed o cancelled) | 90 días después de expires_at | rut y state llegan en null |
| Autorizada | 6 años después de authorized_at | receipt llega en null |
| De una persona cuyo registro venció | Al anonimizar el registro de la persona | rut, state y receipt llegan en null |
Guarda tu copia del comprobante cuando lo recibes. Tu copia sigue siendo verificable con el JWKS por su kid, y la
plataforma conserva la prueba de que lo emitió y cuándo aunque ya no lo entregue.
Cancelar Transacción
Sección titulada «Cancelar Transacción»Cancela una transacción pendiente antes de que la persona la complete:
Endpoint
Sección titulada «Endpoint»POST /{tenantSlug}/pin/transactions/{transaction_id}/cancelHeaders
Sección titulada «Headers»Authorization: Bearer {api_key}Respuesta
Sección titulada «Respuesta»{ "success": true, "data": { "success": true }}Solo transacciones en estado pending pueden cancelarse. Si la transacción ya está en un estado terminal, recibirás un error 409.
Estados de la Transacción
Sección titulada «Estados de la Transacción»| Estado | Terminal | Descripción |
|---|---|---|
pending | No | Persona aún no completó la verificación |
authorized | Sí | La persona se autenticó con la modalidad pedida — trae receipt |
failed | Sí | PIN incorrecto reiterado, PIN bloqueado, o la verificación facial agotó sus intentos (face_not_verified) |
expired | Sí | La persona no completó a tiempo; expiration_reason dice si empezó o no |
cancelled | Sí | Cancelada por el integrador o por la persona |
Webhooks
Sección titulada «Webhooks»Recibirás webhooks cuando la transacción cambie a un estado terminal, en el callback_url de la transacción o, si no lo enviaste, en la URL del webhook de tu cuenta. Sólo recibes los eventos que tu configuración de webhooks tenga en Eventos Habilitados (vacía = todos).
Eventos
Sección titulada «Eventos»| Evento | Cuándo |
|---|---|
pin.transaction.authorized | La persona se autenticó con la modalidad pedida |
pin.transaction.failed | PIN incorrecto 5 veces, PIN bloqueado o verificación facial no completada |
pin.transaction.expired | La transacción venció sin completarse; expiration_reason dice si la persona empezó o no |
pin.transaction.cancelled | Cancelada por integrador o persona |
Payload del Webhook
Sección titulada «Payload del Webhook»{ "event": "pin.transaction.authorized", "timestamp": "2026-05-09T23:38:01.577Z", "data": { "transaction_id": "cedd740a-e5a3-43a2-9896-5ac45dfdb4dc", "integration_id": "702191e3-e415-41dc-ac1b-0b66d2edccf9", "rut": "12.345.678-9", "authentication_method": "pin", "operation_type": "firma_contrato", "operation_label": "Firmar Contrato N° 2026-042", "operation_hash": "06860b21052299ad...", "authorized_at": "2026-05-09T23:38:01.538Z", "status": "authorized", "state": "session-abc-123", "failure_reason": null, "expiration_reason": null, "receipt": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ii4uLiJ9.eyJ0cmFuc2FjY2lvbklkIjoiLi4uIn0.MEUCIQ..." }}Cuando vence una transacción cuya persona empezó y no terminó a tiempo:
{ "event": "pin.transaction.expired", "timestamp": "2026-05-12T10:14:02.101Z", "data": { "transaction_id": "cedd740a-e5a3-43a2-9896-5ac45dfdb4dc", "integration_id": "702191e3-e415-41dc-ac1b-0b66d2edccf9", "rut": "12.345.678-9", "authentication_method": "face", "operation_type": "firma_contrato", "operation_label": "Firmar Contrato N° 2026-042", "operation_hash": "06860b21052299ad...", "authorized_at": null, "status": "expired", "state": "session-abc-123", "failure_reason": "transaction_expired", "expiration_reason": "not_completed" }}Ningún evento lleva el identificador interno de la persona: es propio de la plataforma y la correlacionaría entre
integradores. Todos llevan authentication_method, y receipt va sólo en pin.transaction.authorized: en los
demás eventos la clave no existe.
pin.transaction.expired llega a lo sumo una vez por transacción.
Campos Específicos por Evento
Sección titulada «Campos Específicos por Evento»| Evento | Campos adicionales en data |
|---|---|
pin.transaction.authorized | authorized_at, receipt |
pin.transaction.failed | failure_reason ("max_attempts_reached", "rate_limit_exceeded" o "face_not_verified") |
pin.transaction.expired | failure_reason ("transaction_expired"), expiration_reason ("not_started" o "not_completed") |
pin.transaction.cancelled | — |
Resolución del Webhook URL
Sección titulada «Resolución del Webhook URL»El sistema determina dónde enviar el webhook con esta prioridad:
callback_urlenviado en la transacción (per-request)- URL global del webhook configurado en el tenant (fallback)
- Si ninguno existe → no se envía webhook (solo redirect)
Los headers personalizados configurados en el webhook global del tenant se incluyen siempre, independientemente de si el URL es per-request o global.
Dos reglas deciden si el webhook sale:
- Eventos Habilitados filtra por tipo de evento con cualquiera de los dos destinos. Si la lista no está vacía y no incluye el evento, no se envía, tampoco al
callback_url. Vacía = todos. - Activo gobierna sólo la URL global. Con el interruptor apagado, una transacción con
callback_urlsigue recibiendo sus webhooks; una sincallback_url, no. Para dejar de recibirlos en elcallback_url, deja de enviarlo o quita los eventospin.transaction.*de la lista.
Redirect
Sección titulada «Redirect»Cuando la persona completa la verificación (o cancela), el browser redirige a la redirect_uri con query parameters:
https://miapp.cl/pin-callback?transaction_id=cedd740a-...&status=authorized&state=session-abc-123| Parámetro | Descripción |
|---|---|
transaction_id | UUID de la transacción |
status | Estado final: authorized, failed o cancelled |
state | Valor opaco que enviaste al crear la transacción |
Códigos de Error
Sección titulada «Códigos de Error»| Código | Descripción | Causa |
|---|---|---|
| 400 | Validación fallida | Falta authentication_method o no es pin ni face, RUT inválido, redirect_uri no en lista permitida, callback_url no permitida, máximo de pendientes alcanzado |
| 401 | No autorizado | API key inválida, expirada o revocada |
| 402 | Pago requerido | Tu plan no incluye la métrica de PIN-RUT (code: METRIC_NOT_IN_PLAN) |
| 403 | Prohibido | Servicio PIN_RUT no habilitado o rol insuficiente |
| 404 | No encontrado | Integración no existe o está inactiva, transacción no encontrada |
| 409 | Conflicto | Transacción no está en estado pending (al cancelar); o se envió un PIN a una transacción face (code: face_authentication_required) |
| 422 | No procesable | No hay redirect_uri y la integración no tiene default |
| 423 | Bloqueado | Sólo con authentication_method: "pin": el PIN del RUT está bloqueado por demasiados intentos fallidos. Con face el PIN no interviene y no hay 423 |
Ejemplo de Error 404 (Integración)
Sección titulada «Ejemplo de Error 404 (Integración)»{ "message": "Integración activa con slug \"mi_app_prod\" no encontrada", "error": "Not Found", "statusCode": 404}Ejemplo de Error 423 (RUT Bloqueado)
Sección titulada «Ejemplo de Error 423 (RUT Bloqueado)»{ "code": "pin_blocked", "message": "RUT is blocked due to too many failed attempts", "statusCode": 423}Protección Anti-Brute-Force
Sección titulada «Protección Anti-Brute-Force»El sistema implementa un rate limit global de intentos fallidos del PIN:
- Máximo 10 intentos fallidos de PIN por RUT en 1 hora (cross-tenant). Las verificaciones faciales fallidas no cuentan: no bloquean el PIN
- Al superar el límite, el PIN del RUT se bloquea inmediatamente
- Transacciones nuevas para un RUT bloqueado retornan 423 Locked
- El bloqueo es global: protege contra ataques distribuidos desde múltiples integraciones
Límites
Sección titulada «Límites»| Concepto | Límite |
|---|---|
| Transacciones pendientes por RUT × integración | 3 |
| Intentos de PIN por transacción | 5 |
| Intentos de verificación facial por transacción | 3 |
| Intentos fallidos globales de PIN por RUT/hora | 10 |
| Plazo para abrir el enlace | 7 días desde la creación |
| Plazo para ingresar el PIN, desde que se abre el enlace | 5 minutos |
| Plazo para la prueba de vida, desde que se abre el enlace | 30 minutos |
| Plazo de una acreditación de identidad | Hasta la creación más 7 días y 48 horas (expires_at) |
Tamaño máximo de state | 2048 caracteres |
Tamaño máximo de operation_label | 200 caracteres |
Integración JavaScript
Sección titulada «Integración JavaScript»// 1. Crear transacciónconst response = await fetch( `https://api.redcumbre.cl/${tenantSlug}/pin/transactions`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ integration_slug: 'mi_app_prod', rut: '12.345.678-9', operation_type: 'firma_contrato', operation_label: 'Firmar Contrato N° 2026-042', redirect_uri: 'https://miapp.cl/pin-callback', callback_url: 'https://miapp.cl/webhooks/pin', state: sessionId, }), });
const { data } = await response.json();
// 2. Redirigir al usuariowindow.location.href = `https://app.redcumbre.cl${data.authorize_url}`;
// 3. En la página de callback, verificar resultadoconst params = new URLSearchParams(window.location.search);const txId = params.get('transaction_id');const status = params.get('status');
if (status === 'authorized') { // Verificar server-side antes de tomar acción const result = await fetch( `https://api.redcumbre.cl/${tenantSlug}/pin/transactions/${txId}/result`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); const { data: tx } = await result.json();
if (tx.status === 'authorized') { // Identidad verificada — proceder con la operación }}Receptor de Webhook (Node.js)
Sección titulada «Receptor de Webhook (Node.js)»app.post('/webhooks/pin', (req, res) => { const { event, data } = req.body;
switch (event) { case 'pin.transaction.authorized': console.log(`✓ Identidad verificada: RUT=${data.rut}, tx=${data.transaction_id}`); // Procesar autorización break;
case 'pin.transaction.failed': console.log(`✗ Verificación fallida: ${data.failure_reason}`); break;
case 'pin.transaction.expired': console.log(`⏰ Transacción expirada: ${data.transaction_id}`); break;
case 'pin.transaction.cancelled': console.log(`↩ Transacción cancelada: ${data.transaction_id}`); break; }
res.json({ received: true });});