La solución de Open Finance de Sensedia Chile implementa un Authorisation Server basado en el perfil de seguridad FAPI 2.0 y en las especificaciones internacionales:
RFC 9126 — Pushed Authorization Requests (PAR): Los parámetros de autorización se envían al servidor vía POST seguro, evitando la exposición en la URL del navegador.
RFC 9396 — Rich Authorization Requests (RAR): Permite solicitudes de autorización granulares mediante el parámetro authorization_details, en lugar de scopes genéricos.
Grant Management for OAuth 2.0: Introduce el concepto de grant_id para gestionar el ciclo de vida completo de los consentimientos (crear, actualizar, reemplazar, fusionar, consultar y revocar).
El SFA chileno define cuatro roles principales para los participantes del ecosistema de Open Finance:
| Rol | Sigla | Descripción |
|---|---|---|
| Institución Proveedora de Información | IPI | Entidad que almacena datos de clientes y los expone vía API. |
| Institución Proveedora de Cuentas | IPC | Entidad que aloja cuentas y procesa transacciones de pago. |
| Proveedor de Servicios Basados en Información | PSBI | Tercero receptor de datos (agregador financiero). |
| Proveedor de Servicios de Iniciación de Pagos | PSIP | Empresa autorizada para iniciar pagos desde cuentas de clientes. |
La solución se compone de varios componentes que, en conjunto, cubren los requisitos funcionales y no funcionales exigidos por la regulación chilena.

Sensedia API Management: Punto de entrada con soporte mTLS/TLS. Maneja rate limiting (60 TPM / 10 TPS por endpoint). Gobierna todas las APIs del ecosistema (edición, manejo, customización y capacidades de seguridad requeridas por el regulador).
Motor de consentimiento: Gestiona el ciclo de vida del consentimiento (estados, aprobadores múltiples, expiración) en la visión de IPI y IPC. Grant Management es parte del módulo para administrar grant_id con operaciones de creación, consulta, alteración y cambios.
Auth Server (FAPI 2.0): Implementa PAR, RAR, DCR/DCM y emite tokens OAuth 2.0 en el perfil regulatorio de Chile.
IPI/PSBI Journey (en construcción): Solicita y consume los datos proporcionados por los IPI para ofrecer productos o servicios nuevos o mejorados a los usuarios finales. Remueve la complejidad de integración entre participantes del ecosistema.
Cumplimiento regulatorio: La solución cubre los requisitos funcionales y no funcionales (almacenamiento en caché del directorio central, gestión de webhooks, gestión de certificados, entre otros) requeridos para los roles de IPI y IPC.

La oferta incluye integración con el Directorio Central de la CMF, almacenando caché de los datos para garantizar precisión en las consultas. Se implementan mecanismos de actualización del caché conforme a la regulación. La copia local del participante es una réplica obligatoria que cada PSBI, PSIP, IPI e IPC mantiene en su infraestructura.
Ventana máxima entre sincronizaciones: 8 horas.
Este flujo debe ejecutarse cada vez que exista un aviso de actualización y, por defecto, en caso de no haber aviso, con una frecuencia mínima de 8 horas para cumplir con los requisitos del Perfil de Seguridad y del Anexo 3.
La copia local no es solo una lista de participantes — es el conjunto completo de datos que alimenta todos los flujos OIDC/FAPI:
Identificación de los participantes: datos legales y estados operativos (Activo/Normal, Activo/Mecanismo Alternativo, Inactivo/Desconectado, Suspendido, etc.).
Certificados digitales: mTLS de transporte + JWS de firma, con sus kid, vigencia y estado (activo, obsoleto, revocado).
JWKS públicos para validación cruzada (incluyendo el JWKS del propio Directorio para validar SSAs en DCR).
Servidores de autorización: emisor, URL de openid-configuration, endpoints de PAR/token, soporte a DCR.
Recursos API y versiones: familia (Accounts, Payments, etc.), ApiResourceId, ApiVersion, endpoints concretos.
El flujo para la gestión del consentimiento se divide en dos partes principales: OIDC (Autorización) y Gestión de Consentimiento. Ambos flujos, juntos, forman los pasos necesarios para la generación de credenciales de acceso a las APIs y, a continuación, la posibilidad de gestionar los tokens para el acceso a las APIs de Negocio.
Comencemos con OIDC: aunque es el nombre más técnico, podemos entender este flujo como el flujo inicial para la generación de credenciales.

Responsable de la autorización y emisión de tokens a partir del modelo OpenID Connect (OIDC).
El SFA adopta OIDC con una versión restringida por el Perfil FAPI 2.0 de la OpenID Foundation.
Permitidos:
Authorization Code Flow con PAR + JAR + RAR — el camino estándar para todos los consentimientos con interacción del usuario.
Refresh Token — solo si está asociado a un grant existente.
DCR — Dynamic Client Registration para la primera interacción con el servidor.
| Método | Endpoint | Descripción |
|---|---|---|
| POST | /par |
Pushed Authorization Request con RAR |
| GET | /authorize |
Redirect para autenticación y consentimiento |
| POST | /token |
Intercambia code por access_token + grant_id |
| GET | /.well-known/openid-configuration |
Discovery metadata |
| POST | /clients-registrations/openid-connect |
Dynamic Client Registration |
El grant_id es el identificador único de cada consentimiento. Las acciones de grant_management_action permiten manipular un grant existente:
| Acción | Descripción | Requiere auth del usuario |
|---|---|---|
create |
Genera nuevo consentimiento y grant_id |
Sí |
update |
Extiende duración sin alterar scope | No (si solo vigencia) |
replace |
Sustituye permissions por un conjunto nuevo | Sí |
merge |
Combina permisos nuevos con existentes | Sí |
query |
Consulta estado actual | No |
revoke |
Cancela consentimiento e invalida tokens | No |
| Estado | Descripción |
|---|---|
| AwaitingAuthorisation | Pendiente de autorización del Usuario Final |
| AwaitingMultiAuthorisation | Requiere múltiples aprobaciones |
| Authorised | Aprobado y activo |
| Rejected | Rechazado por el Usuario Final |
| Revoked | Revocado post-aprobación |
| Incomplete | Firmas conjuntas no completadas a tiempo |
| Expired | Vigencia expirada |
Vigencia: un único uso, 7 días, 1 mes, 3 meses, 6 meses, 12 meses, o mientras dure el contrato.

Las reglas de Granularidad y Dependencia definen una jerarquía para el acceso a los endpoints de las APIs, determinando la profundidad del acceso a los datos financieros y los permisos previos necesarios.
La granularidad divide el nivel de detalle de la información en hasta tres niveles de profundidad:
1er nivel (Primer Nivel): Representa el permiso de acceso general y amplio a una categoría de productos. Se utiliza para consultar la lista de recursos disponibles de un cliente. Por ejemplo, acceder a la lista de todas las cuentas (GET /accounts/v1), tarjetas de crédito (GET /credit-card-accounts/v1/accounts) o préstamos (GET /loans/v1).
2do nivel (Segundo Nivel): Permite enfocarse en un producto específico dentro de esa lista, utilizando su identificador único. Por ejemplo, consultar los datos de solo una cuenta específica a través del endpoint GET /accounts/v1/{accountID}.
3er nivel (Tercer Nivel): Es el grado más profundo de acceso, orientado a consultar detalles operativos específicos y sensibles de ese recurso previamente identificado, como saldos, límites e historial de transacciones. Ejemplos incluyen los endpoints para saldos de cuentas (GET /accounts/v1/{accountID}/balance) o transacciones de inversiones (GET /investments/v1/{investmentID}/transactions).
La dependencia crea un sistema de prerrequisitos en cascada. No se puede conceder un acceso más granular sin que los niveles jerárquicamente superiores también formen parte del alcance autorizado:
El acceso al 1er nivel tiene dependencia "N/A" (No aplicable), ya que es la capa base y no depende de ningún otro permiso.
El acceso al 2do nivel tiene como dependencia "1er". Esto significa que, para consultar los detalles de un producto específico (como /{accountID}), el sistema exige que la institución también posea el permiso para consultar la lista general de cuentas de ese cliente.
El acceso al 3er nivel tiene la dependencia "1er y 2do". Para que la institución pueda leer saldos, transacciones o límites, necesita obligatoriamente las autorizaciones conjuntas para listar las cuentas (1er nivel) y acceder a la cuenta por su ID único (2do nivel).
| Nivel | Ejemplo | Dependencia |
|---|---|---|
| 1er | GET /accounts/v1 |
N/A |
| 2do | GET /accounts/v1/{accountId} |
1er nivel |
| 3er | GET /accounts/v1/{accountId}/balances |
1er y 2do |
Para obtener más detalles sobre los niveles de acceso y los permisos, consulte la documentación oficial.
Endpoints REST para listar, consultar, auditar y revocar grants identificados por grant_id.
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /grants |
Lista grants activos |
| GET | /grants/{grant_id} |
Detalle de un grant |
| DELETE | /grants/{grant_id} |
Revoca un grant |
| POST | /grants/{grant_id}/revoke |
Revoca (alternativa) |
| GET | /grants/history |
Historial de auditoría |
La plataforma notifica al backend de la institución, de forma asíncrona, cada cambio relevante en el ciclo de vida de un consentimiento. La entrega es por HTTP POST hacia el endpoint que la institución registra como callback URL, con envoltura CloudEvents 1.0 y cuerpo application/cloudevents+json.
Esta funcionalidad se encuentra bajo construcción. La descripción a continuación refleja el contrato vigente y puede sufrir cambios hasta su publicación.
Existen dos flujos de eventos con contratos, namespaces y públicos diferentes:
| Canal | Entre quiénes | Namespace | Origen del contrato |
|---|---|---|---|
| Regulado | Plataforma ↔ demás participantes del SFA y Directorio | sfa.consent.* |
NCG N°569 / Anexo Técnico N°3 |
| De producto (este documento) | Plataforma → backend de la institución | sensedia.consent.* |
Contrato propio de la solución Sensedia |
El namespace sensedia.consent.* es deliberadamente distinto del regulatorio: los tipos de este catálogo no cambian porque la CMF publique los suyos.
El type nombra el hecho ocurrido, no el estado resultante — el estado viaja en data.newState. Un mismo estado puede ser resultado de hechos distintos, y hay hechos que no cambian el estado.
Tipo (sensedia.consent.) |
Hecho que lo dispara | data.newState |
Acción esperada del receptor | Terminal | CMF |
|---|---|---|---|---|---|
created |
El consentimiento fue creado y espera la autorización del titular | AwaitingAuthorisation |
Mostrar como pendiente; todavía no hay acceso | No | No |
authorised |
El titular aprobó; el consentimiento queda activo | Authorised |
Habilitar el consumo de las APIs con el token | No | Sí |
rejected |
El titular denegó un consentimiento que aún estaba pendiente | Rejected |
Cerrar la jornada; no reintentar sobre el mismo consentimiento | Sí | Sí |
revoked |
Un consentimiento activo fue revocado | Revoked |
Detener el acceso de inmediato | Sí | Sí |
expired |
Terminó la vigencia del consentimiento | Expired |
Detener el acceso; continuar exige un consentimiento nuevo | Sí | No — en el canal regulado llega como revoked con newState=Expired |
replaced |
El alcance del consentimiento fue sustituido por completo | Authorised — sin cambio |
Releer el detalle: los permisos vigentes cambiaron | No | No |
merged |
Se agregaron permisos al alcance vigente | Authorised — sin cambio |
Releer el detalle: los permisos vigentes cambiaron | No | No |
Terminal significa que no habrá más eventos para ese consent:{grantId}. La columna CMF indica si el hecho está tipificado por la normativa: sólo authorised, revoked y rejected lo están; los demás existen únicamente en este contrato de producto.
replaced y merged no cambian el estado: son los únicos casos en que previousState y newState vienen iguales. Un receptor que sólo observe el estado los ignoraría y quedaría con una visión desactualizada de lo que el titular consintió. Ante ellos, relea el detalle en GET {source}/grants/{grantId}.
El catálogo publica solamente hechos que ocurren: no se anticipan tipos para flujos que la plataforma todavía no ejecuta. Cuando se incorporen, entrarán como tipos nuevos — cambio retrocompatible, cubierto por la obligación del receptor de tolerar tipos desconocidos.
CloudEvents 1.0 simplificado. Los atributos de extensión van en minúsculas y sin separadores porque así lo exige la especificación.
| Campo | Obligatorio | Descripción |
|---|---|---|
specversion |
Sí | Versión de CloudEvents — siempre "1.0" |
contractversion |
Sí | Versión mayor de este contrato (hoy "1"). No confundir con specversion |
type |
Sí | El hecho ocurrido, en el namespace sensedia.consent.* |
source |
Sí | Emisor (issuer del realm). Base del callback de detalle: GET {source}/grants/{grantId} |
subject |
Sí | Identificador del consentimiento, en el formato consent:{grantId} |
id |
Sí | UID RFC 4122 del evento — igual al encabezado x-idempotency-key |
time |
Sí | Instante de la ocurrencia (ISO-8601 UTC), no de la entrega |
datacontenttype |
Sí | application/json |
role |
No | Rol en que actuó la plataforma: IPI/IPC (el cambio nació aquí) o PSBI/PSIP (llegó de un participante externo y se retransmite) |
consenttype |
No | SHARE (compartición de datos) o PAYMENT (iniciación de pago) |
originorgid |
No | El IPI/IPC de origen. Presente sólo cuando role es PSBI o PSIP |
data |
Sí | { previousState, newState, reason? } — objeto abierto, puede ganar campos nuevos |
event |
Sí | { streamId, sequence } — metadatos de ordenamiento |
role, consenttype y originorgid viajan en la envoltura, y no dentro de data, porque describen el origen del evento y no el cambio de estado: así el receptor (o un gateway intermedio) puede enrutar y filtrar sin deserializar data.
Los roles del SFA ya vienen separados por dominio, de modo que role y consenttype están emparejados — IPI/PSBI siempre con SHARE, e IPC/PSIP siempre con PAYMENT. Las combinaciones cruzadas no existen.
El evento transporta cinco estados, que son un subconjunto de la máquina de estados del consentimiento, con el mismo nombre y el mismo significado que el status devuelto por la Grant Management API — no hay traducción que hacer:
AwaitingAuthorisation · Authorised · Rejected · Revoked · Expired
newState es el estado completo, no un delta — por eso aplicar last-writer-wins por event.sequence es seguro. previousState es nulo cuando el consentimiento acaba de ser creado.
El campo reason es opcional y no exhaustivo: trátelo como texto libre, nunca como enum cerrado. Valores en uso hoy: psu_denied (denegado por el usuario), psu_request (revocado a solicitud del usuario) y expired (fin de la vigencia).
Eventos livianos (thin): data lleva solamente la transición de estado — sin PII, sin RUT y sin montos. El detalle (permisos, finalidad, vigencia) se obtiene bajo demanda en GET {source}/grants/{grantId}.
POST /webhooks/consent HTTP/1.1
Content-Type: application/cloudevents+json
Authorization: Bearer <access_token>
x-idempotency-key: a1b2c3d4-0011-4455-8899-aabbccddeeff
x-webhook-interaction-id: 761bab4e-0179-11ee-be56-0242ac120002
{
"specversion": "1.0",
"contractversion": "1",
"type": "sensedia.consent.revoked",
"source": "https://as.sensedia.example/realms/institucion",
"subject": "consent:9d8a7c6b-1e2f-4a3b-8c9d-0e1f2a3b4c5d",
"id": "a1b2c3d4-0011-4455-8899-aabbccddeeff",
"time": "2026-07-27T14:30:00Z",
"datacontenttype": "application/json",
"role": "PSBI",
"consenttype": "SHARE",
"originorgid": "org-987654",
"data": {
"previousState": "Authorised",
"newState": "Revoked",
"reason": "psu_request"
},
"event": {
"streamId": "consent:9d8a7c6b-1e2f-4a3b-8c9d-0e1f2a3b4c5d",
"sequence": 3
}
}
La ruta /webhooks/consent es ilustrativa: vale la callback URL que la institución registre en la suscripción.
| Encabezado | Contenido | ¿Cambia en el reintento? |
|---|---|---|
Authorization |
Bearer <token> emitido por el API Gateway que publica la integración |
— |
x-idempotency-key |
UID RFC 4122 del evento, igual al campo id. Es la clave de deduplicación |
No |
x-webhook-interaction-id |
UID RFC 4122 del intento (opcional). Sirve para correlacionar logs, no para deduplicar | Sí |
Content-Type |
application/cloudevents+json |
— |
Deduplique siempre por x-idempotency-key. Deduplicar por x-webhook-interaction-id — que cambia en cada intento — haría que las reentregas legítimas se procesaran como eventos nuevos.
La entrega es solamente push (no hay API de consulta en esta fase) y con garantía al menos una vez: las reentregas son esperadas, por lo que la idempotencia es obligatoria en el receptor.
Reintentos: 1 / 2 / 5 / 15 / 60 minutos.
Presupuesto total: 24 horas; agotado el presupuesto, el evento pasa a dead-letter y genera alerta operacional.
Timeout: ~10 s por intento.
| Respuesta del receptor | Interpretación del emisor |
|---|---|
2xx (se recomienda 202) |
ACK — evento aceptado. El cuerpo no es leído por el emisor |
| 4xx | Falla permanente, sin reintento — el evento pasa directamente a dead-letter |
| 408 y 429 | Excepciones: se tratan como falla transitoria, con reintento. En 429 el receptor puede enviar Retry-After |
| 5xx o timeout | Falla transitoria, con reintento según el cronograma |
Cuándo responder: el ACK confirma que el receptor aceptó la responsabilidad por el evento, no que ya terminó de procesarlo. Responda al aceptar — persista o encole el evento y devuelva 2xx de inmediato, procesando de forma asíncrona. Procesar antes de responder consume el presupuesto de timeout del intento: si se agota, el emisor lo trata como falla transitoria y reintenta, generando una entrega duplicada de algo que el receptor ya estaba procesando. El ACK es síncrono, en la respuesta del propio POST: no existe confirmación diferida.
El contrato no tipa el cuerpo de error a propósito — el emisor sólo registra la clase del status en log. Si el receptor quiere devolver detalle, sugerimos RFC 9457 (application/problem+json).
No se garantiza el orden de llegada. Cada consentimiento es un stream propio (event.streamId), con numeración monotónica y sin saltos (event.sequence) asignada en el origen, en el orden real de los cambios de estado. La entrega, sin embargo, no está serializada: si un intento entra en reintento, el evento siguiente del mismo consentimiento puede llegar antes. Es deliberado — serializar bloquearía el stream completo detrás de un receptor no disponible.
Por lo tanto, el receptor debe:
Deduplicar por x-idempotency-key, con una ventana de al menos 24 horas.
Reconstruir el orden por event.sequence, nunca por el orden de llegada: guardar el mayor sequence ya aplicado por streamId y descartar el que llegue con valor menor o igual (evento antiguo o reentrega).
Usar la ausencia de saltos para detectar que un evento anterior aún no ha llegado.
Como data.newState es el estado completo, aplicar last-writer-wins por sequence es seguro. El evento es la autoridad sobre el estado; el callback sirve para el detalle (permisos y metadatos), no para confirmar el estado.
La entrega es siempre sobre HTTPS (TLS 1.2+, se recomienda 1.3). La plataforma se autentica con un access token en el encabezado Authorization: Bearer, emitido por el API Gateway que publica la integración (OAuth2 client credentials).
La validación del token puede quedar en el propio API Gateway — es lo habitual cuando el endpoint de recepción se publica en él, y en ese caso el backend del receptor no necesita repetir la verificación. La exigencia es que ninguna solicitud llegue al procesamiento sin haber sido autenticada: si el endpoint también es alcanzable por fuera del gateway, el backend debe validar por su cuenta.
No hay firma del payload ni cifrado a nivel de aplicación: los eventos son livianos y no llevan datos sensibles.
El objeto data es abierto y va a crecer: se agregarán campos nuevos conforme evolucione el producto, como cambio retrocompatible. Por eso el receptor debe:
Ignorar campos desconocidos en data y en la envoltura, en lugar de rechazar el mensaje. Los deserializadores estrictos deben relajarse (ej. Jackson FAIL_ON_UNKNOWN_PROPERTIES=false, JsonSerializerOptions permisivo en .NET).
No depender del orden de las claves ni de la ausencia de un campo.
Tolerar valores nuevos de type y de estado: los tipos no reconocidos deben ignorarse respondiendo 2xx, nunca con 4xx/5xx — eso generaría reintentos y dead-letter innecesarios.
El evento identifica su versión en contractversion, con la versión mayor del contrato (hoy "1"). Un receptor que necesite ramificar por versión debe hacerlo sobre ese valor, nunca sobre specversion.
| Tipo de cambio | Ejemplos | Efecto |
|---|---|---|
| Retrocompatible | Agregar campos a data o a la envoltura; agregar tipos de evento; agregar valores de estado o de reason |
No incrementa contractversion |
| Incompatible | Quitar o renombrar un campo; cambiar la semántica de un campo existente; cambiar el significado de un type ya publicado |
Genera una versión mayor nueva |
Una versión mayor nueva se anuncia con antelación y ambas conviven durante al menos 12 meses, para que la institución migre sin ventana de indisponibilidad. Nunca se reutiliza un nombre de type ya publicado con semántica distinta.
Sobre una eventual divergencia futura con la CMF: el namespace es independiente y estos tipos no cambian porque la CMF publique los suyos. Si la normativa define un homónimo con significado distinto, se trata como cambio incompatible (versión mayor nueva, comunicación previa y convivencia); si sólo agrega un estado que no teníamos, entra como tipo nuevo del catálogo — cambio retrocompatible.
Registro y verificación de la suscripción del endpoint.
Eventos del Directorio.
Eventos de pago (la transacción). Nota: el consentimiento de pago sí está cubierto — usa el mismo catálogo, la misma máquina de estados y el mismo subject; la única diferencia es consenttype: PAYMENT.
Recuperación de eventos por el receptor (feed de reconciliación tipo desde={checkpoint}): no existe en esta fase. La recuperación depende de los reintentos automáticos dentro de la ventana de 24 h — una indisponibilidad mayor puede exigir reconciliación manual entre las partes.
Now is your time to rock!
Actualizado en agosto/2026