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:
- RabbitMQ directo — vincula tu propia cola a los exchanges AMQP de Midaz.
- Streaming Hub — suscríbete a través del servicio gestionado de fan-out de Lerian.
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 |
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.
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) |
Estos exchanges vienen deshabilitados por defecto. El operador que ejecuta Midaz debe habilitar cada exchange antes de que emita eventos.
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:
{
"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).
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:
GET /v1/catalog
Authorization: Bearer <jwt>
{
"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:
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
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:
{
"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:
GET /v1/events?subscription_id=<uuid>&limit=100
Authorization: Bearer <jwt>
{
"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.