ADR-018: Interoperabilidad saliente por webhooks firmados¶
Estado: Aceptado Fecha: 2026-06-22 Autores: Giampiero (mantenedor principal)
Contexto¶
E11 (interoperabilidad) debe permitir que sistemas externos —portales, ERPs, otros SGD, operadores postales (E20)— reaccionen a lo que ocurre en OrpycaMCP (radicado creado, trámite asignado/vencido, expediente cerrado/transferido…) sin acoplarse al bus interno (Redis Streams) ni sondear la API. El SGDEA ya emite un sobre de evento canónico (orpycamcp.*.events, ADR-010) que notification-service consume.
La pregunta es cómo exponer esos eventos al exterior de forma segura, multi-tenant y desacoplada.
Decisión¶
Entregar los eventos de dominio a sistemas externos mediante webhooks salientes HTTP, firmados con HMAC-SHA256, suscritos por tenant, construidos sobre el bus de eventos existente.
- Suscripciones por tenant — tabla
webhook_subscriptionentenant_{slug}:url,event_types(lista; vacía = todos),secret(clave HMAC),active. CRUD bajo/api/v1/webhooks(notification-service, que ya consume el bus). - Entrega — al consumir un evento, notification-service hace
POSTdel sobre canónico (event_type, tenant_slug, payload, ts) a cada suscripción activa cuyoevent_typescoincida, con cabeceraX-OrpycaMCP-Signature: sha256=<hmac(secret, body)>para que el receptor verifique autenticidad e integridad. - Desacople y resiliencia — la entrega es best-effort con reintentos y nunca bloquea el procesamiento del evento (un webhook caído no afecta al SGDEA). El bus interno sigue siendo la fuente de verdad.
- Propietario — notification-service (es el servicio de entrega saliente: ya consume los streams y tiene reintentos). No se crea un servicio nuevo.
- Importación / pull (entrada de datos externos, exportaciones masivas FUID/índice) se abordan como incrementos separados de E11; este ADR fija el mecanismo saliente event-driven.
Consecuencias¶
Positivas:
- Integración externa desacoplada: los terceros se suscriben a eventos; no acceden al bus interno ni sondean.
- Seguridad: la firma HMAC permite al receptor verificar origen e integridad sin exponer credenciales; el secret es por suscripción.
- Multi-tenant nativo: las suscripciones viven por tenant_{slug} (ADR-002); un tenant no ve ni recibe los eventos de otro.
- Reutiliza el sobre canónico (ADR-010) y la infraestructura de reintentos de notification-service; cero servicios nuevos.
- Habilita E20 (operadores postales) y futuras integraciones como suscriptores más, sin cambiar el núcleo.
Negativas / límites: - Entrega at-least-once best-effort: el receptor debe ser idempotente (se incluye un id de evento). No hay garantía de orden estricto entre webhooks. - Webhooks salientes implican una superficie SSRF (URLs arbitrarias): se mitiga restringiendo esquemas/hosts en configuración y registrando entregas; el endurecimiento fino queda como evolutivo. - La cola de reintentos persistente (DLQ) y el panel de entregas son incrementos posteriores; el MVP reintenta en línea y registra el resultado.
Alternativas consideradas¶
- Exponer Redis Streams directamente al exterior: descartado; acopla a los terceros al transporte interno y rompe el aislamiento.
- Solo polling (los terceros consultan la API): descartado como única vía; no es reactivo y carga la API; se mantiene la API REST como complemento.
- Bus externo / broker dedicado (Kafka, RabbitMQ) para terceros: sobredimensionado para la escala objetivo; webhooks HTTP firmados son el estándar de facto y suficientes (coherente con ADR-014: empezar simple, puerta de salida abierta).