Cada evento tiene una clave (credits.decision, wallets.topup.completed, pqrs.responded…) que usas al suscribir un endpoint y que llega en la cabecera X-Webhook-Event y en el campo event del cuerpo. Esta página es una foto del catálogo al 1 de noviembre de 2026 (52 eventos). La fuente de verdad es el catálogo en vivo, GET/v1/gateway/webhooks/event-catalog/ : si aparece un evento nuevo, lo verás primero ahí, con su payload_example.

El sobre de los eventos #

Los eventos namespaced (todas las categorías salvo general) llegan con el mismo sobre:

JSON
{
  "event": "wallets.topup.completed",
  "version": "1",
  "tenant_id": "nova",
  "occurred_at": "2026-10-27T15:00:00Z",
  "resource": { "…": "campos propios del evento" }
}
  • event: la clave del catálogo. Es la que debes usar para enrutar.
  • version: versión mayor del esquema de resource, como texto. Agregar un campo opcional no la cambia, así que tu código debe ignorar los campos que no conoce. Solo un cambio incompatible sube la versión (ver Deprecaciones).
  • tenant_id: código de tu organización.
  • occurred_at: cuándo ocurrió el hecho, en UTC. Úsalo para ordenar: las entregas no llegan necesariamente en orden.
  • resource: los datos del evento. Su forma exacta está en el payload_example de cada evento.

Montos como texto #

Todos los montos viajan como texto decimal ("amount": "20000.00"), nunca como número de punto flotante. Conviértelos con un tipo decimal (Decimal en Python, decimal en C#, big.Rat o shopspring/decimal en Go, bcmath en PHP) y no con float. Más en Montos.

Privacidad #

Los payloads se diseñan para viajar por internet:

  • Billetera: nunca incluyen PAN, CVV ni el documento completo; los teléfonos van enmascarados (phone_number_masked) y las tarjetas solo traen los últimos 4 dígitos.
  • Salud: resource nunca lleva el documento completo ni el nombre del paciente.
  • PQRS: no lleva el texto de la respuesta ni el documento; consulta el detalle por API.
  • Administrativo: sin diagnósticos ni datos bancarios.

Eventos clásicos (categoría general) #

Los eventos del ciclo de crédito clásico (application.*, credit.*, compliance.* e installment.due_soon) son anteriores al sobre: no traen resource y sus campos vienen en el nivel superior del cuerpo. Con los SDK léelos desde el cuerpo completo:

SDKSobre namespacedEvento clásico
Pythonevent.resource["decision"]event.payload["score"]
JavaScript / TSevent.resource.decisionevent.payload.score
PHP$event->resource()['decision']$event->payload['score']
.NETevt.Resource["decision"]evt.Payload["score"]
Goev.Resource()["decision"]ev.Payload["score"]

(score es solo ilustrativo: consulta el payload_example de cada evento clásico en el catálogo.)

Alias legado #

subsidy.application.received es el nombre antiguo de subsidies.application.received. El gateway lo sigue aceptando al suscribir y lo normaliza a la clave canónica, que es la que verás en tus endpoints y en las entregas. Los SDK traen el mapa de alias y una función para normalizar: canonical_event_type (Python), canonicalEventType (JS), Webhook::canonicalEventType (PHP), CompenseWebhooks.CanonicalEventType (.NET) y webhook.CanonicalEventType (Go).

El evento de prueba #

test.ping no está en el catálogo (no se suscribe): es el tipo que reciben tus endpoints cuando pides una prueba sin event_type. Las pruebas traen "test": true y un event_id que empieza por test-.

Catálogo completo #

Créditos #

EventoDesdeCuándo se emite
credits.application.received2026-06-01Se radicó una solicitud de crédito en el gateway y quedó aceptada para proceso.
credits.decision2026-06-01Un asesor o el motor resolvió la solicitud (decision, approved o rejected).
credits.disbursed2026-06-01Se desembolsaron los fondos del crédito aprobado.

Subsidios #

EventoDesdeCuándo se emite
subsidies.application.received2026-06-01Se radicó una solicitud de subsidio (alias legado subsidy.application.received).
subsidies.decision2026-06-01Un asesor o el motor de reglas resolvió la solicitud de subsidio.
subsidies.disbursed2026-06-01Se pagó un subsidio aprobado.

Cuota monetaria #

EventoDesdeCuándo se emite
monetary_allowance.generated2026-06-01Se generó la cuota monetaria de un afiliado para un período.
monetary_allowance.disbursed2026-06-01Se pagó una cuota monetaria generada.

Afiliados #

EventoDesdeCuándo se emite
affiliates.synced2026-06-01Terminó una sincronización de afiliados (resumen de creados, actualizados y retirados).

Billetera #

EventoDesdeCuándo se emite
wallets.cashin.initiated2026-07-21Se registró una intención de recarga; aún no se mueve dinero.
wallets.cashin.confirmed2026-07-21El origen confirmó la recarga y se emitió dinero electrónico a la billetera.
wallets.transfer.created2026-07-21Se creó una transferencia (P2P, Bre-B o entre bolsillos) y quedó asentada en el libro mayor.
wallets.transfer.settled2026-07-21La transferencia quedó liquidada.
wallets.transfer.failed2026-07-21El riel rechazó la transferencia; no se movió dinero.
wallets.disbursement.credited2026-07-21Se abonó un subsidio o crédito directamente a la billetera.
wallets.withdrawal.authorized2026-07-21Se autorizó un retiro en cajero (token de un solo uso y fondos retenidos).
wallets.bill_payment.completed2026-07-21El titular pagó una factura de servicios y el facturador la aceptó.
wallets.qr_payment.completed2026-07-21El titular pagó un QR interoperable (EMVCo).
wallets.transaction.reversed2026-07-21Se reversó una transacción (reembolso).
wallets.dispute.opened2026-07-21El titular abrió una disputa sobre una transacción.
wallets.dispute.resolved2026-07-21Un operador resolvió la disputa (a favor, con reverso, o rechazada).
wallets.card.issued2026-07-21Se emitió una tarjeta (solo viajan los últimos 4 dígitos).
wallets.topup.completed2026-10-27Se asentó una recarga de celular (carrier, teléfono enmascarado, amount).

Cartera (pagos y mora) #

EventoDesdeCuándo se emite
payment.received2026-10-27Se registró un pago del crédito (cuota, abono o pago total) con su canal y reference.
payment.applied2026-10-27El pago se distribuyó por la prelación (capital, intereses, mora, seguros, impuestos).
installment.overdue2026-10-27Una cuota pasó a mora (se emite una vez por cuota, con days_overdue).
loan.paid_off2026-10-27El crédito quedó en cero (paz y salvo).

Servicios de salud #

EventoDesdeCuándo se emite
appointment.booked2026-10-27Se agendó una cita por el gateway.
appointment.cancelled2026-10-27Se canceló una cita y se liberó el cupo.
appointment.rescheduled2026-10-27Se movió una cita a otro cupo (previous trae la fecha y hora anteriores).
appointment.status_changed2026-10-27Se registró la asistencia (completed o no_show).
health_billing.payment.registered2026-10-27Se aplicó un pago del pagador (EPS) a una factura de salud.

Administrativo #

EventoDesdeCuándo se emite
admin.hr.novelty.received2026-10-27Se registró una novedad de talento humano.
admin.payroll.novelty.received2026-10-27Se cargó una novedad de nómina.
admin.accounting.entry.drafted2026-10-27Se creó un comprobante contable en borrador.
admin.treasury.document.registered2026-10-27Se registró un recaudo, egreso o cuenta de cobro en borrador (kind).
admin.treasury.statement.imported2026-10-27Se cargó un extracto bancario.

PQRS #

EventoDesdeCuándo se emite
pqrs.created2026-10-27Se radicó una PQRS.
pqrs.status_changed2026-10-27Cambió el estado de una PQRS (incluye pasar a resolved por una respuesta final).
pqrs.responded2026-10-27La entidad registró una respuesta oficial.

Ciclo de crédito clásico (categoría general, sin sobre namespaced) #

EventoDesdeCuándo se emite
application.submitted—El solicitante terminó y envió la solicitud.
application.scored—Resultados de buró, scoring y reglas del motor de decisión.
application.approved—Aprobada por un analista o automáticamente por el motor de reglas.
application.rejected—Rechazada por el motor de riesgo, cumplimiento o un analista.
application.manual_review_required—Enviada a la bandeja de revisión humana.
credit.activated—El crédito quedó activo tras el desembolso.
credit.disbursed—El proveedor confirmó la transferencia de los fondos.
credit.restructured—Se aprobó un cambio de condiciones del crédito.
credit.written_off—El crédito se castigó como pérdida irrecuperable.
installment.due_soon—Una cuota vence en los próximos 5 días.
compliance.exception_raised—Alerta SARLAFT, UIAF u otra excepción regulatoria.
compliance.ros_submitted—Se envió un reporte de operación sospechosa (ROS) a la UIAF.

Eventos compartidos entre cartera y el ciclo clásico

payment.received, payment.applied e installment.overdue aparecen en la categoría loans (cartera, desde el 27 de octubre de 2026) y comparten nombre con el ciclo de crédito clásico. Suscribe la clave una vez; el payload_example del catálogo te dice qué forma tiene.

Lee el payload_example en vivo #

Antes de escribir el manejador de un evento, pide su ejemplo al catálogo: es exactamente la forma que llegará a tu receptor.

import json
import os
from compense_gateway import CompenseClient

client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")

catalogo = client.webhooks.event_catalog(category="loans")
ejemplo = next(e for e in catalogo["events"] if e["event"] == "payment.received")
print(json.dumps(ejemplo["payload_example"], indent=2, ensure_ascii=False))

En el sandbox el catálogo es de muestra

Con una llave de sandbox, el catálogo devuelve 9 eventos representativos marcados con "sample": true y un payload_example genérico. Para ver el catálogo completo y los ejemplos reales usa una llave de producción en el ambiente dev. Ver Probar tus webhooks.

Ejemplos de payload #

wallets.topup.completed (billetera) #

Se emite cuando una recarga de celular queda asentada, en la misma transacción del movimiento.

JSON
{
  "event": "wallets.topup.completed",
  "version": "1",
  "tenant_id": "nova",
  "occurred_at": "2026-10-27T15:00:00Z",
  "resource": {
    "transaction_id": "c3d4e5f6-0000-4a1b-9c2d-000000000123",
    "wallet_id": "7a8b9c0d-0000-4a1b-9c2d-000000000045",
    "carrier": "CLARO",
    "phone_number_masked": "******4567",
    "amount": "20000.00",
    "currency": "COP",
    "status": "completed"
  }
}

Los demás eventos de billetera documentan estos campos en resource:

EventoCampos de resource
wallets.transfer.createdtransaction_id, source_wallet_id, recipient_wallet_id (null si es a otro banco), amount, currency, rail_reference, is_internal, status; Bre-B añade rail: "breb"
wallets.transfer.failedtransaction_id, source_wallet_id, amount, currency, rail_reference, reason, status
wallets.bill_payment.completedtransaction_id, wallet_id, service_id, amount, currency, provider, provider_reference, status

payment.received (cartera) #

Se emite cuando se registra un pago de un crédito (cuota, abono o pago total). Su event_id es determinista por recibo: un reintento de tu petición nunca produce un segundo webhook del mismo pago.

JSON
{
  "event": "payment.received",
  "version": "1",
  "tenant_id": "horizonte",
  "occurred_at": "2026-10-28T14:05:00Z",
  "resource": {
    "credit_id": "5d6e7f80-0000-4a1b-9c2d-000000000777",
    "loan_number": "CRD-00123",
    "payment_id": "9e0f1a2b-0000-4a1b-9c2d-000000000901",
    "receipt_id": "7c8d9e0f-0000-4a1b-9c2d-000000000902",
    "affiliate_id": "1b2c3d4e-0000-4a1b-9c2d-000000000310",
    "kind": "installment",
    "channel": "ACH_LIBRANZA",
    "reference": "HORIZONTE-REC-2026-000901",
    "amount": "456789.00",
    "value_date": "2026-10-28",
    "received_at": "2026-10-28T14:05:00Z"
  }
}
Evento de carteraCampos de resource
payment.appliedapplied_to_capital, applied_to_interest, applied_to_penalties, applied_to_insurance, applied_to_taxes, installments[], outstanding_principal_after, loan_status_after
loan.paid_offcredit_id, loan_number, affiliate_id, last_payment_id, channel, paid_off_at
installment.overduecredit_id, loan_number, affiliate_id, installment_number, amount, due_date, days_overdue (una vez por cuota)

pqrs.responded (PQRS) #

Se emite cuando la entidad registra una respuesta oficial, venga de la app, del panel o del API.

JSON
{
  "event": "pqrs.responded",
  "version": "1",
  "tenant_id": "horizonte",
  "occurred_at": "2026-10-29T16:20:00Z",
  "resource": {
    "pqrs_id": "4f0c1d2e-0000-4a1b-9c2d-000000000123",
    "radicado": "PQRS-2026-000123",
    "response_id": "9a1e2b3c-0000-4a1b-9c2d-000000000456",
    "is_final": true,
    "status": "resolved",
    "external_reference": "HORIZONTE-CRM-88231",
    "source": "gateway",
    "actor": "integration:<llave> agent:ana.p"
  }
}

pqrs.created trae además issue_type, service_area, status, priority, origin_channel, affiliate_id y deadline; pqrs.status_changed trae from_status, to_status y priority.

Las guías de cada módulo documentan sus eventos: billetera, cartera, salud, administrativo y PQRS.

Enruta por tipo de evento #

Un mismo endpoint puede recibir muchos eventos: enruta por event y deja pasar (con 2xx) los que no manejas, para que un evento nuevo no te genere reintentos. Los SDK traen la foto del catálogo para validaciones rápidas (EVENT_TYPES en Python, JS y PHP, CompenseWebhooks.EventTypes en .NET y webhook.EventTypes() / webhook.IsKnownEventType() en Go), pero nunca rechazan un evento que no conocen.

from compense_gateway.webhooks import WebhookEvent

def manejar(event: WebhookEvent) -> None:
    if event.event == "wallets.topup.completed":
        registrar_recarga(event.resource["transaction_id"], event.resource["amount"])
    elif event.event == "payment.received":
        conciliar_pago(event.resource["receipt_id"], event.resource["amount"])
    elif event.event == "pqrs.responded":
        actualizar_caso(event.resource["radicado"], event.resource["status"])
    # cualquier otro evento: ignóralo y responde 2xx igual

El receptor completo (verificación de firma, deduplicación y respuesta rápida) está en Verificar la firma.