Ir al contenido

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

Antes de usar la API necesitas:

  1. Una API Key con rol FULL-API
  2. El tenant debe tener el servicio PIN_RUT habilitado
  3. Una integración PIN-RUT configurada con las URLs permitidas

Antes de crear transacciones, debes configurar una integración desde el panel de administración:

Configuración → PIN-RUT → Integraciones

CampoDescripción
SlugIdentificador único inmutable (ej: mi_app_prod). Se usa en cada request.
NombreNombre descriptivo que la persona verá en la pantalla de verificación
URIs de redirectURLs HTTPS donde el browser redirige tras completar la verificación
URLs de callbackURLs HTTPS donde se envían webhooks con el resultado
Orígenes iframeURLs HTTPS autorizadas para embeber la pantalla PIN en un iframe
URI de redirect defaultRedirect por defecto si no se especifica en cada transacción
CampoValor
Slugmi_app_prod
NombreMi Aplicación
Redirect URIshttps://miapp.cl/pin-callback
Callback URLshttps://miapp.cl/webhooks/pin
Redirect defaulthttps://miapp.cl/pin-callback

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:

  1. Tu sistema crea una transacción con los datos de la operación y la modalidad con que la persona autoriza
  2. Rediriges al usuario a la authorize_url retornada
  3. La persona ve los datos de la operación y se autentica con la modalidad que pediste
  4. Tu sistema recibe el resultado vía webhook y/o redirect
  5. Opcionalmente, consultas el resultado vía API
authentication_methodLa persona autoriza conCuándo usarla
pinSu 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ónOperaciones frecuentes, donde el PIN basta
faceUna prueba de vida contra el reconocimiento facial de su identidad acreditada. El PIN no interviene: no se pide, y un PIN bloqueado no la impideOperaciones 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.


POST /{tenantSlug}/pin/transactions
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"
}
CampoTipoRequeridoDescripción
integration_slugstringSlug de la integración configurada
rutstringRUT chileno de la persona (ej: 12.345.678-9 o 12345678-9)
authentication_methodstringCon qué autoriza la persona: pin o face. No tiene valor por defecto: sin él, 400
operation_typestringTipo de operación en snake_case (máx. 50 chars)
operation_labelstringDescripción visible para la persona (máx. 200 chars)
operation_detailstringNoDescribe 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_uristringNoURL HTTPS de redirect. Debe estar en las URIs permitidas de la integración
callback_urlstringNoURL HTTPS para webhook. Debe estar en las URLs permitidas de la integración
statestringNoValor opaco devuelto sin modificar en redirect y webhook (máx. 2048 chars)
Ventana de terminal
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"
}'
{
"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..."
}
}
CampoTipoDescripción
transaction_idstring (UUID)Identificador único de la transacción
authorize_urlstringRuta 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_atstring (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_hashstringHash SHA-256 de la operación (para verificación de integridad)

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-5e6d7c8b9a0f

La pantalla le muestra primero la operación y quién la pide, y después la lleva por lo que le falte:

  • Con pin y PIN: ingresa su PIN.
  • Con face, o con pin y sin PIN: se autentica con su cara. Con pin, 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.


GET /{tenantSlug}/pin/transactions/{transaction_id}/result
Authorization: Bearer {api_key}
{
"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."
}
}
}

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ó.

enrollment_charge informa el cargo de la acreditación de identidad que esta transacción gatilló.

  • Es null cuando esa persona ya estaba acreditada y no hubo acreditación. null significa no hubo cargo, no el cargo fue cero.
  • Cuando la tarifa del plan no se puede resolver, llega con "available": false y su message, 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.

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 null cuando 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": false y su message, 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.

expiration_reason dice por qué venció una transacción:

ValorCuá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
nullLa 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ónCuándoQué cambia en la consulta
No autorizada (expired, failed o cancelled)90 días después de expires_atrut y state llegan en null
Autorizada6 años después de authorized_atreceipt llega en null
De una persona cuyo registro vencióAl anonimizar el registro de la personarut, 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.


Cancela una transacción pendiente antes de que la persona la complete:

POST /{tenantSlug}/pin/transactions/{transaction_id}/cancel
Authorization: Bearer {api_key}
{
"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.


EstadoTerminalDescripción
pendingNoPersona aún no completó la verificación
authorizedLa persona se autenticó con la modalidad pedida — trae receipt
failedPIN incorrecto reiterado, PIN bloqueado, o la verificación facial agotó sus intentos (face_not_verified)
expiredLa persona no completó a tiempo; expiration_reason dice si empezó o no
cancelledCancelada por el integrador o por la persona

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).

EventoCuándo
pin.transaction.authorizedLa persona se autenticó con la modalidad pedida
pin.transaction.failedPIN incorrecto 5 veces, PIN bloqueado o verificación facial no completada
pin.transaction.expiredLa transacción venció sin completarse; expiration_reason dice si la persona empezó o no
pin.transaction.cancelledCancelada por integrador o persona
{
"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.

EventoCampos adicionales en data
pin.transaction.authorizedauthorized_at, receipt
pin.transaction.failedfailure_reason ("max_attempts_reached", "rate_limit_exceeded" o "face_not_verified")
pin.transaction.expiredfailure_reason ("transaction_expired"), expiration_reason ("not_started" o "not_completed")
pin.transaction.cancelled

El sistema determina dónde enviar el webhook con esta prioridad:

  1. callback_url enviado en la transacción (per-request)
  2. URL global del webhook configurado en el tenant (fallback)
  3. 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_url sigue recibiendo sus webhooks; una sin callback_url, no. Para dejar de recibirlos en el callback_url, deja de enviarlo o quita los eventos pin.transaction.* de la lista.

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ámetroDescripción
transaction_idUUID de la transacción
statusEstado final: authorized, failed o cancelled
stateValor opaco que enviaste al crear la transacción

CódigoDescripciónCausa
400Validación fallidaFalta 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
401No autorizadoAPI key inválida, expirada o revocada
402Pago requeridoTu plan no incluye la métrica de PIN-RUT (code: METRIC_NOT_IN_PLAN)
403ProhibidoServicio PIN_RUT no habilitado o rol insuficiente
404No encontradoIntegración no existe o está inactiva, transacción no encontrada
409ConflictoTransacción no está en estado pending (al cancelar); o se envió un PIN a una transacción face (code: face_authentication_required)
422No procesableNo hay redirect_uri y la integración no tiene default
423BloqueadoSó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
{
"message": "Integración activa con slug \"mi_app_prod\" no encontrada",
"error": "Not Found",
"statusCode": 404
}
{
"code": "pin_blocked",
"message": "RUT is blocked due to too many failed attempts",
"statusCode": 423
}

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

ConceptoLímite
Transacciones pendientes por RUT × integración3
Intentos de PIN por transacción5
Intentos de verificación facial por transacción3
Intentos fallidos globales de PIN por RUT/hora10
Plazo para abrir el enlace7 días desde la creación
Plazo para ingresar el PIN, desde que se abre el enlace5 minutos
Plazo para la prueba de vida, desde que se abre el enlace30 minutos
Plazo de una acreditación de identidadHasta la creación más 7 días y 48 horas (expires_at)
Tamaño máximo de state2048 caracteres
Tamaño máximo de operation_label200 caracteres

// 1. Crear transacción
const 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 usuario
window.location.href = `https://app.redcumbre.cl${data.authorize_url}`;
// 3. En la página de callback, verificar resultado
const 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
}
}
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 });
});