> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Recepción de eventos de Midaz

> Elige entre vincular directamente a los exchanges de RabbitMQ de Midaz o suscribirte a través del Streaming Hub para consumir eventos del ledger.

Midaz emite un flujo de eventos de dominio cada vez que ocurre algo relevante en el ledger: se registra una transacción, un saldo entra en overdraft, se crea una cuenta. Si tu sistema necesita reaccionar a esos cambios (conciliación, notificaciones, proyecciones derivadas, analítica), puedes consumir ese flujo de eventos en lugar de hacer polling a la API REST.

Esta guía cubre las dos formas admitidas de recibir eventos de Midaz y cómo elegir entre ellas:

1. **RabbitMQ directo** — vincula tu propia cola a los exchanges AMQP de Midaz.
2. **Streaming Hub** — suscríbete a través del servicio gestionado de fan-out de Lerian.

## Las dos formas en resumen

***

Midaz publica cada evento de dominio en **dos transportes** en paralelo:

* Una publicación en **RabbitMQ (AMQP)** hacia topic exchanges gestionados por el despliegue de Midaz.
* Una publicación en un **bus de eventos interno**, que el Streaming Hub consume y distribuye (fan-out) a los suscriptores por tenant.

Esto te da dos superficies de integración para los *mismos* eventos subyacentes:

| Evento        | RabbitMQ directo                      | Streaming Hub                                        |
| ------------- | ------------------------------------- | ---------------------------------------------------- |
| Transporte    | AMQP 0-9-1                            | Webhook / Pull (HTTP) / SQS / RabbitMQ / EventBridge |
| Te conectas a | El broker de Midaz                    | Una API REST del plano de control                    |
| Acoplamiento  | Fuerte (infra de Midaz)               | Débil (edge gestionado)                              |
| Ideal para    | Despliegues self-hosted / co-ubicados | Integradores externos / SaaS                         |

<Note>
  Si no operas tu propio broker de Midaz, usa el Streaming Hub. RabbitMQ directo es para integradores que se ejecutan dentro o junto al despliegue de Midaz y son dueños del broker.
</Note>

## Opción A — RabbitMQ directo

***

### Cómo funciona

El ledger de Midaz publica eventos a través de un productor interno hacia un conjunto de **topic exchanges**. Un consumidor vincula su propia cola al exchange correspondiente con un patrón de routing key y consume sobre AMQP.

Datos del publicador (del servicio de ledger):

* **Content type:** `application/json`
* **Delivery mode:** persistente
* **Headers:** el contexto de traza de OpenTelemetry se inyecta en cada mensaje
* **Tenancy:** el productor es multi-tenant — los mensajes se publican en un **vhost específico por tenant**

Exchanges de eventos y sus routing keys:

| Exchange                                  | Patrón de routing key                                               | Emite                                        |
| ----------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------- |
| `transaction.transaction_events.exchange` | `midaz.transaction.<STATUS>` (p. ej. `midaz.transaction.APPROVED`)  | Envelopes del ciclo de vida de transacciones |
| `transaction.overdraft_events.exchange`   | `midaz.balance.overdraft.<action>` (`drawn` / `repaid` / `cleared`) | Eventos de overdraft                         |
| `audit.append_log.exchange`               | `audit.append_log.key`                                              | Entradas del log de auditoría (append-log)   |

<Note>
  **Estos exchanges vienen deshabilitados por defecto.** El operador que ejecuta Midaz debe habilitar cada exchange antes de que emita eventos.
</Note>

### Para operadores self-hosted

Cada exchange se controla con una variable de entorno dedicada, todas con valor por defecto `false`:

| Exchange                                  | Variable de entorno                   |
| ----------------------------------------- | ------------------------------------- |
| `transaction.transaction_events.exchange` | `RABBITMQ_TRANSACTION_EVENTS_ENABLED` |
| `transaction.overdraft_events.exchange`   | `RABBITMQ_OVERDRAFT_EVENTS_ENABLED`   |
| `audit.append_log.exchange`               | `AUDIT_LOG_ENABLED`                   |

Define la variable en `true` en la configuración de tu despliegue de Midaz para activar el exchange correspondiente.

### Estructura del mensaje

Los eventos de transacción envuelven el objeto de dominio en un envelope:

```json theme={null}
{
  "source": "midaz",
  "eventType": "transaction",
  "action": "APPROVED",
  "timeStamp": "2026-07-06T12:00:00Z",
  "version": "<midaz build version>",
  "organizationId": "...",
  "ledgerId": "...",
  "payload": { "...full transaction JSON..." }
}
```

La routing key codifica la acción (`midaz.transaction.APPROVED`, `midaz.balance.overdraft.drawn`), de modo que puedes vincular de forma acotada en lugar de filtrar en el código.

### Ejemplo de configuración

Los parámetros de conexión provienen de la configuración de RabbitMQ del despliegue de Midaz (`RABBITMQ_HOST`, `RABBITMQ_PORT_AMQP`, un usuario consumidor como `RABBITMQ_CONSUMER_USER`, el `RABBITMQ_VHOST` por tenant y TLS opcional vía `RABBITMQ_TLS`).

```python theme={null}
import pika

params = pika.URLParameters("amqps://consumer:***@midaz-rabbitmq:5671/<tenant-vhost>")
conn = pika.BlockingConnection(params)
ch = conn.channel()

# El exchange lo declara Midaz; decláralo de forma pasiva o replica su topología.
exchange = "transaction.transaction_events.exchange"

# Vincula una cola durable propia a las routing keys que te interesan.
ch.queue_declare(queue="my-consumer.transactions", durable=True)
ch.queue_bind(
    queue="my-consumer.transactions",
    exchange=exchange,
    routing_key="midaz.transaction.*",   # todos los estados
)

def on_message(chan, method, props, body):
    handle(body)                          # application/json
    chan.basic_ack(method.delivery_tag)   # ack nativo, por mensaje

ch.basic_qos(prefetch_count=10)
ch.basic_consume(queue="my-consumer.transactions", on_message_callback=on_message)
ch.start_consuming()
```

### Cuándo usarlo

* **Operas o co-ubicas** el despliegue de Midaz y ya eres dueño del broker.
* Quieres **semántica AMQP nativa** — `ack`/`nack` por mensaje, prefetch/QoS, dead-letter exchanges que controlas, consumidores en competencia sobre una misma cola.
* Tu stack ya es nativo de RabbitMQ y quieres el salto de menor latencia, dentro del clúster.

### Contrapartidas

* Te acopla a la topología y credenciales del broker interno de Midaz.
* Los exchanges están **deshabilitados por defecto** y el operador debe habilitarlos.
* Sin reintentos/dead-letter/auto-desactivación gestionados — la fiabilidad de entrega es tu responsabilidad del lado del consumidor.
* No es viable para un tercero que solo tiene acceso de red a un Midaz alojado.

## Opción B — Streaming Hub

***

### Cómo funciona

El Streaming Hub es el edge de entrega con fan-out de Lerian. Consume eventos del bus de eventos interno de Midaz y entrega cada evento coincidente a un **sink de suscriptor por tenant** que registras. Nunca tocas Kafka ni el broker: registras una suscripción a través de un plano de control REST y eliges cómo quieres recibir los eventos.

Tipos de sink admitidos:

* **`webhook`** — el hub hace POST de cada evento a tu endpoint HTTPS, **firmado con HMAC** con un secreto de firma por suscripción.
* **`pull`** — haces polling a `GET /v1/events`; la propia lectura es el acuse de recibo (cursor-as-ack). No se requiere endpoint entrante.
* **`sqs`**, **`rabbitmq`**, **`eventbridge`** — el hub entrega en *tu* cola de AWS, *tu* broker de RabbitMQ o *tu* bus de EventBridge.

El hub añade fiabilidad de entrega sobre el flujo bruto: un inbox durable con deduplicación de consumo único, reintentos por sink con backoff, dead-lettering al agotarse y auto-desactivación de endpoints que fallan de forma persistente.

### Eventos disponibles

Consulta el catálogo para ver los tipos de evento a los que puedes suscribirte:

```http theme={null}
GET /v1/catalog
Authorization: Bearer <jwt>
```

```json theme={null}
{
  "events": [
    {
      "eventType": "transaction.created",
      "topic": "lerian.streaming.transaction.created",
      "schemaVersion": "1.0.0",
      "schemaMajor": 1,
      "description": "A transaction was created."
    }
  ]
}
```

Midaz publica un catálogo amplio con claves `<resource>.<event>` (todos en el esquema `1.0.0`), que incluye:

* `organization.*`, `ledger.*`, `account.*`, `asset.*`, `portfolio.*`, `segment.*` (`created` / `updated` / `deleted`)
* `operation-route.*`, `transaction-route.*` (`created` / `updated` / `deleted`)
* `balance.created`, `balance.config-changed`, `balance.deleted`
* `balance.overdraft-drawn`, `balance.overdraft-repaid`, `balance.overdraft-cleared`
* `transaction.posted`, `transaction.committed`, `transaction.canceled`, `transaction.reverted`

### Autenticación

El plano de control es **100% JWT de `lib-auth` (Bearer)**: el hub no emite credenciales propias. Presenta un JWT emitido por plugin-auth en cada llamada a `/v1`:

```http theme={null}
Authorization: Bearer <plugin-auth JWT>
```

El tenant se deriva únicamente de los claims validados del JWT (`tenantId`, con `owner` como alternativa), nunca del cuerpo de la solicitud. Un cliente de máquina obtiene su token del flujo de **client-credentials** de plugin-auth (un id/secreto de aplicación que se intercambia por un token de acceso de corta duración).

### Ejemplo de configuración — suscripción webhook

```http theme={null}
POST /v1/subscriptions
Authorization: Bearer <jwt>
X-Idempotency: <unique-key>
Content-Type: application/json

{
  "name": "my-webhook",
  "sink_kind": "webhook",
  "endpoint": "https://hooks.example.com/lerian",
  "event_types": ["transaction.posted", "transaction.committed"],
  "schema_major": 1,
  "plan_tier": "standard"
}
```

`201 Created` devuelve la suscripción **y el secreto de firma una sola vez** — guárdalo de inmediato, solo se puede rotar, nunca volver a leer:

```json theme={null}
{
  "subscription": { "id": "0190b8e2-...", "verification_state": "active", "...": "..." },
  "signingSecret": "whsec_...ONCE..."
}
```

Verifica la firma HMAC en cada webhook entrante con ese secreto antes de confiar en el payload. Usa `POST /v1/subscriptions/:id/ping` para enviar un evento sintético firmado y confirmar que tu endpoint está bien conectado.

### Ejemplo de configuración — suscripción pull (apta para serverless)

Créala con `"sink_kind": "pull"` (omite `endpoint` — el servidor lo sintetiza) y luego haz polling:

```http theme={null}
GET /v1/events?subscription_id=<uuid>&limit=100
Authorization: Bearer <jwt>
```

```json theme={null}
{
  "events": [
    { "seq": 42, "ceId": "...", "eventType": "transaction.posted",
      "receivedAt": "2026-07-06T12:00:00Z", "payload": { "...": "..." } }
  ],
  "next_cursor": 42
}
```

La lectura es el ack: el `seq` más alto devuelto avanza un cursor durable y monótono. Reenvía el `next_cursor` emitido por el servidor como `?after=` en la siguiente llamada. Cada evento lleva `ceId` para tu propia deduplicación.

### Sinks de cola (SQS / RabbitMQ / EventBridge)

Las suscripciones de tipo cola se crean **sin** credencial y nacen en `pending_verification`: no emiten nada hasta que proporcionas una credencial de salida vía `PUT /v1/subscriptions/:id/credential`, que el hub prueba (conexión + autenticación) y, si tiene éxito, cambia la suscripción a `active`. Para un sink de RabbitMQ el `endpoint` es `"<exchange>/<routingKey>"` y el host del broker vive en la credencial cifrada; para sinks de AWS, obtén la política de confianza de IAM + `ExternalId` desde `GET /v1/subscriptions/:id/setup-artifacts` y configura la concesión entre cuentas antes del PUT de la credencial.

### Cuándo usarlo

* Eres un **integrador externo** con solo acceso de red a un Midaz alojado.
* Quieres entrega **serverless** — un endpoint webhook o un bucle de pull HTTP, sin broker que operar.
* Quieres **fiabilidad gestionada** — deduplicación, reintentos/backoff, dead-lettering, auto-desactivación, webhooks firmados — sin construirla tú mismo.
* Quieres distribuir los mismos eventos hacia infraestructura **nativa de AWS** (SQS/EventBridge).

### Limitaciones

* Sin transporte SSE ni WebSocket — la entrega es push por webhook o pull HTTP (más los sinks de cola).
* El catálogo es **global** (idéntico sin importar qué tenant se autentique).
* Los consumidores pull son dueños de la posición de su cursor — un avance más allá del cursor omite ese hueco de forma permanente. Usa `?after=` para reproducir desde cualquier `seq` anterior.

***

## Tabla de decisión

| Criterio                     | RabbitMQ directo                                                     | Streaming Hub                                                            |
| ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Tu stack**                 | Ya es nativo de RabbitMQ / AMQP, dentro del clúster                  | Cualquier cosa que hable HTTPS (o AWS SQS/EventBridge)                   |
| **Modelo de despliegue**     | Operas / co-ubicas Midaz y eres dueño del broker                     | Midaz alojado / SaaS, integrador externo                                 |
| **Ack granular**             | ✅ `ack`/`nack` nativo por mensaje, prefetch, DLX                     | Parcial — 2xx del webhook o cursor-as-ack del pull; sin nack por mensaje |
| **Serverless**               | ❌ Requiere un consumidor AMQP de larga duración                      | ✅ Endpoint webhook o bucle de pull sin estado                            |
| **Simplicidad para empezar** | ❌ Creds del broker, vhost, exchange que hay que habilitar            | ✅ Un `POST /v1/subscriptions` autenticado                                |
| **Fiabilidad gestionada**    | ❌ Reintentos / DLQ / backoff a tu cargo                              | ✅ Deduplicación, reintentos/backoff, dead-letter, auto-desactivación     |
| **Latencia**                 | La más baja (salto dentro del clúster)                               | Ligeramente mayor (edge de fan-out)                                      |
| **Modelo de auth**           | Usuario/contraseña del broker (+ TLS opcional)                       | JWT de plugin-auth (Bearer); webhooks firmados                           |
| **Alcance de eventos**       | Exchanges de transacción / overdraft / auditoría (deben habilitarse) | Catálogo completo de Midaz vía `/v1/catalog`                             |
| **Distribuir a AWS**         | ❌ Constrúyelo tú mismo                                               | ✅ Sinks de SQS / EventBridge                                             |

**Regla general:** si eres dueño del broker y quieres control AMQP en crudo, usa **RabbitMQ directo**. En cualquier otro caso — integración externa, serverless, fiabilidad gestionada, entrega a AWS — usa el Streaming Hub.

***

## Consideraciones de seguridad

* **Credenciales de mínimo privilegio.** Para RabbitMQ directo, conéctate con un usuario **con alcance de consumidor** (no el usuario publicador/por defecto), restringido al vhost del tenant, y habilita TLS (`amqps://`). Para el Streaming Hub, limita la aplicación de plugin-auth al tenant que representa y rota el secreto de cliente.
* **Verifica las firmas de los webhooks.** Valida siempre la firma HMAC-v1 con tu secreto de firma por suscripción antes de actuar sobre un webhook. Trata una solicitud sin firma o con firma no coincidente como hostil.
* **Protege el secreto de firma.** Se muestra una sola vez al crear/rotar y luego no se puede recuperar. Guárdalo en un gestor de secretos; si se filtra, rótalo vía `POST /v1/subscriptions/:id/secret/rotate` (solapamiento de doble firma de 24 h).
* **Endpoints solo HTTPS.** Los sinks de webhook deben ser `https://`. El hub valida los endpoints contra SSRF al crear y en el PUT de credencial; se rechazan los destinos en texto plano/privados.
* **El aislamiento de tenant se deriva de los claims.** El hub lee la identidad del tenant solo de los claims validados del JWT (o `ce-tenantid` en la ingesta), nunca del cuerpo de la solicitud — no intentes pasar un `tenant_id` en los payloads.
* **Idempotencia y deduplicación.** Envía `X-Idempotency` en las llamadas mutantes del plano de control. En el plano de datos, deduplica por `ceId` (Streaming Hub) o por el id del mensaje / tu propia clave (RabbitMQ), ya que ambos transportes son al-menos-una-vez.
* **No expongas las interioridades de Midaz.** El exchange interno de operaciones de saldo y las credenciales del broker nunca deben compartirse con consumidores externos; pon a los terceros detrás del Streaming Hub.
