Un webhook es una petición POST que Compense hace a una URL tuya cada vez que ocurre un evento de negocio en tu entidad: se aprobó un crédito, se confirmó una recarga, se aplicó un pago de cartera, se agendó una cita, se respondió una PQRS. En vez de consultar el API cada cierto tiempo (polling), tu sistema se entera en segundos y solo cuando hay algo nuevo.

Todo se administra por API, con tu llave: registras tus endpoints, eliges los eventos, rotas el secreto de firma, envías eventos de prueba, revisas el historial de entregas y reintentas las fallidas. No necesitas entrar al panel.

Solo recibes eventos de lo que tienes activo

Los webhooks son una capacidad del gateway y no dependen de un módulo contratado. Pero solo llegan eventos de los módulos que tu entidad tiene activos (créditos, subsidios, billetera, cartera, salud…), porque solo esos módulos los generan.

Cómo funciona una entrega #

  1. Ocurre un cambio en tu entidad (por API, desde el panel o desde la app del afiliado). El evento se escribe en la misma transacción que el cambio: no hay webhook de algo que no quedó registrado.
  2. Compense hace POST a cada endpoint tuyo activo y suscrito a ese evento, con el cuerpo JSON firmado.
  3. Tu receptor verifica la firma sobre el cuerpo crudo, deduplica por X-Webhook-Id, responde 2xx en menos de 10 segundos y procesa en segundo plano.
  4. Si tu receptor no responde 2xx (o no responde a tiempo), Compense reintenta la misma entrega con espera exponencial. Si agota los reintentos, la entrega queda abandoned y puedes reintentarla a mano.
sequenceDiagram
    autonumber
    participant M as Motor Compense
    participant D as Despachador de webhooks
    participant R as Tu receptor
    participant Q as Tu cola / BD
    M->>D: Evento (misma transacción del cambio)
    D->>R: POST /compense/webhooks (cuerpo JSON + X-Webhook-Signature)
    R->>R: HMAC-SHA256(secreto, cuerpo crudo) == firma?
    alt Firma inválida o timestamp fuera de ventana
        R-->>D: 401
    else Firma válida
        R->>Q: ¿X-Webhook-Id ya procesado? si no, encolar
        R-->>D: 200 (en menos de 10 s)
    end
    opt Respuesta no 2xx, timeout o error de red
        Note over D: Reintentos: 30 s, 2 min, 8 min, 32 min, ~2 h
        D->>R: POST (misma entrega, mismo X-Webhook-Id)
        Note over D: Sin éxito tras max_retries → abandoned
    end

Entrega at-least-once #

Compense garantiza que cada evento se entrega al menos una vez, no exactamente una vez. Un reintento automático puede llegar aunque tu primera respuesta sí se haya procesado (por ejemplo, si tu 200 se perdió en la red). Por eso tu receptor debe ser idempotente:

  • Guarda el X-Webhook-Id (el event_id) de cada evento procesado y descarta los repetidos.
  • No asumas orden entre eventos. Usa occurred_at y el estado que trae resource para decidir, y ante la duda consulta el recurso en el API.

El sobre del payload #

Los eventos namespaced (credits.*, subsidies.*, monetary_allowance.*, affiliates.*, wallets.*, cartera, salud, administrativo y pqrs.*) comparten el mismo sobre:

JSON
{
  "event": "credits.decision",
  "version": "1",
  "tenant_id": "horizonte",
  "occurred_at": "2026-10-27T15:20:00Z",
  "resource": { "…": "campos propios del evento" }
}
CampoQué es
eventClave del evento en el catálogo. Coincide con la cabecera X-Webhook-Event.
versionVersión mayor del esquema de resource. Un campo opcional nuevo no la cambia.
tenant_idCódigo de tu organización.
occurred_atCuándo ocurrió el hecho (ISO-8601, UTC).
resourceLos datos del evento. Montos como texto decimal ("20000.00"), nunca como número.

Los eventos clásicos del ciclo de crédito (categoría general: application.*, credit.*, compliance.*, installment.due_soon) son anteriores al sobre y traen sus campos en el nivel superior del cuerpo, sin resource. Los SDK te dan el cuerpo completo en payload para leerlos. Detalle y ejemplos en Catálogo de eventos.

Cabeceras de cada entrega #

CabeceraValor
X-Webhook-Signaturesha256=<hex>: HMAC-SHA256 del cuerpo crudo con tu secreto, en hexadecimal minúscula
X-Webhook-EventTipo de evento (credits.decision, test.ping, …)
X-Webhook-Idevent_id de la entrega: tu clave de deduplicación
X-Webhook-TimestampSegundos Unix del envío (no entra en la firma)
Content-Typeapplication/json
User-AgentCompense-Webhooks/1.0

Cómo verificarlas paso a paso, con el SDK y sin él, en Verificar la firma.

Ciclo de vida de un endpoint #

Un endpoint pasa por cuatro estados. Los dos primeros los controlas tú; los otros dos los pone Compense según cómo responde tu receptor.

stateDiagram-v2
    [*] --> active: crear
    active --> paused: PATCH active=false
    paused --> active: PATCH active=true
    active --> failing: 10 o más entregas en 24 h, menos de la mitad exitosas
    active --> disabled: 410 Gone repetido
    failing --> disabled: 410 Gone repetido
    disabled --> [*]: crea otro endpoint
EstadoSignificado
activeRecibe eventos.
pausedLo pausaste con active: false. No recibe eventos y conserva su historial.
failingCon 10 o más entregas en 24 horas, menos del 50 % fue exitosa. Revisa tu receptor.
disabledLo deshabilitó Compense (p. ej. tras 3 respuestas 410 Gone). No se reactiva por API: crea otro o escribe a soporte.

Lo que necesitas antes de empezar #

  • Una llave de API con el scope webhooks:read (consultar) y webhooks:manage (crear, editar, rotar, probar y reintentar).
  • Una URL HTTPS pública para tu receptor. No se aceptan localhost, IP privadas ni dominios internos; para desarrollar en tu máquina mira Probar tus webhooks.
  • Un lugar seguro para el secreto de firma (whsec_...): se muestra una sola vez.

En esta sección #