Each event has a key (credits.decision, wallets.topup.completed, pqrs.responded…) that you use when subscribing an endpoint and that arrives in the X-Webhook-Event header and in the body’s event field. This page is a snapshot of the catalog as of November 1, 2026 (52 events). The source of truth is the live catalog, GET/v1/gateway/webhooks/event-catalog/ : if a new event shows up, you’ll see it there first, along with its payload_example.

The event envelope #

Namespaced events (every category except general) arrive with the same envelope:

JSON
{
  "event": "wallets.topup.completed",
  "version": "1",
  "tenant_id": "nova",
  "occurred_at": "2026-10-27T15:00:00Z",
  "resource": { "…": "event-specific fields" }
}
  • event: the catalog key. This is what you should route on.
  • version: major version of the resource schema, as a string. Adding an optional field does not change it, so your code must ignore fields it doesn’t recognize. Only a breaking change bumps the version (see Deprecations).
  • tenant_id: your organization’s code.
  • occurred_at: when the event happened, in UTC. Use it for ordering: deliveries do not necessarily arrive in order.
  • resource: the event data. Its exact shape is in each event’s payload_example.

Amounts as strings #

All amounts travel as decimal strings ("amount": "20000.00"), never as floating-point numbers. Convert them with a decimal type (Decimal in Python, decimal in C#, big.Rat or shopspring/decimal in Go, bcmath in PHP), not with float. More in Amounts.

Privacy #

Payloads are designed to travel over the internet:

  • Wallet: they never include the PAN, CVV or the full ID document number; phone numbers are masked (phone_number_masked) and cards only carry the last 4 digits.
  • Health: resource never carries the patient’s full ID document number or name.
  • PQRS: it doesn’t carry the response text or the ID document; fetch the details through the API.
  • Administrative: no diagnoses or bank details.

Classic events (general category) #

The classic credit lifecycle events (application.*, credit.*, compliance.* and installment.due_soon) predate the envelope: they don’t carry resource and their fields come at the top level of the body. With the SDKs, read them from the full body:

SDKNamespaced envelopeClassic event
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 is just illustrative: check the payload_example of each classic event in the catalog.)

Legacy alias #

subsidy.application.received is the old name of subsidies.application.received. The gateway still accepts it when subscribing and normalizes it to the canonical key, which is the one you’ll see on your endpoints and in deliveries. The SDKs ship the alias map and a normalization function: canonical_event_type (Python), canonicalEventType (JS), Webhook::canonicalEventType (PHP), CompenseWebhooks.CanonicalEventType (.NET) and webhook.CanonicalEventType (Go).

The test event #

test.ping isn’t in the catalog (you can’t subscribe to it): it’s the type your endpoints receive when you request a test without event_type. Tests carry "test": true and an event_id that starts with test-.

Full catalog #

Credits #

EventSinceWhen it's emitted
credits.application.received2026-06-01A credit application was submitted through the gateway and accepted for processing.
credits.decision2026-06-01An advisor or the engine decided the application (decision, approved or rejected).
credits.disbursed2026-06-01The approved credit’s funds were disbursed.

Subsidies #

EventSinceWhen it's emitted
subsidies.application.received2026-06-01A subsidy application was submitted (legacy alias subsidy.application.received).
subsidies.decision2026-06-01An advisor or the rules engine decided the subsidy application.
subsidies.disbursed2026-06-01An approved subsidy was paid out.

Monetary allowance #

EventSinceWhen it's emitted
monetary_allowance.generated2026-06-01An affiliate’s monetary allowance was generated for a period.
monetary_allowance.disbursed2026-06-01A generated monetary allowance was paid out.

Affiliates #

EventSinceWhen it's emitted
affiliates.synced2026-06-01An affiliate sync finished (summary of affiliates created, updated and withdrawn).

Wallet #

EventSinceWhen it's emitted
wallets.cashin.initiated2026-07-21A cash-in intent was recorded; no money has moved yet.
wallets.cashin.confirmed2026-07-21The source confirmed the cash-in and e-money was issued to the wallet.
wallets.transfer.created2026-07-21A transfer (P2P, Bre-B or between pockets) was created and posted to the ledger.
wallets.transfer.settled2026-07-21The transfer was settled.
wallets.transfer.failed2026-07-21The rail rejected the transfer; no money moved.
wallets.disbursement.credited2026-07-21A subsidy or credit was paid directly into the wallet.
wallets.withdrawal.authorized2026-07-21An ATM withdrawal was authorized (single-use token and funds on hold).
wallets.bill_payment.completed2026-07-21The holder paid a utility bill and the biller accepted it.
wallets.qr_payment.completed2026-07-21The holder paid an interoperable QR code (EMVCo).
wallets.transaction.reversed2026-07-21A transaction was reversed (refund).
wallets.dispute.opened2026-07-21The holder opened a dispute on a transaction.
wallets.dispute.resolved2026-07-21An operator resolved the dispute (in the holder’s favor, with a reversal, or rejected).
wallets.card.issued2026-07-21A card was issued (only the last 4 digits are sent).
wallets.topup.completed2026-10-27A mobile top-up was posted (carrier, masked phone number, amount).

Loan portfolio (payments and arrears) #

EventSinceWhen it's emitted
payment.received2026-10-27A loan payment (installment, extra payment or full payoff) was recorded with its channel and reference.
payment.applied2026-10-27The payment was allocated by priority order (principal, interest, late interest, insurance, taxes).
installment.overdue2026-10-27An installment became overdue (emitted once per installment, with days_overdue).
loan.paid_off2026-10-27The loan balance reached zero (paid in full).

Health services #

EventSinceWhen it's emitted
appointment.booked2026-10-27An appointment was booked through the gateway.
appointment.cancelled2026-10-27An appointment was cancelled and its slot was released.
appointment.rescheduled2026-10-27An appointment was moved to another slot (previous carries the previous date and time).
appointment.status_changed2026-10-27Attendance was recorded (completed or no_show).
health_billing.payment.registered2026-10-27A payer (EPS) payment was applied to a health invoice.

Administrative #

EventSinceWhen it's emitted
admin.hr.novelty.received2026-10-27An HR adjustment was recorded.
admin.payroll.novelty.received2026-10-27A payroll adjustment was uploaded.
admin.accounting.entry.drafted2026-10-27A draft journal entry was created.
admin.treasury.document.registered2026-10-27A draft collection, outgoing payment or billing statement was recorded (kind).
admin.treasury.statement.imported2026-10-27A bank statement was uploaded.

PQRS #

EventSinceWhen it's emitted
pqrs.created2026-10-27A PQRS was filed.
pqrs.status_changed2026-10-27A PQRS changed status (including moving to resolved through a final response).
pqrs.responded2026-10-27The entity recorded an official response.

Classic credit lifecycle (general category, no namespaced envelope) #

EventSinceWhen it's emitted
application.submitted—The applicant completed and submitted the application.
application.scored—Credit bureau, scoring and decision-engine rule results are available.
application.approved—Approved by an analyst or automatically by the rules engine.
application.rejected—Rejected by the risk engine, compliance or an analyst.
application.manual_review_required—Sent to the human review queue.
credit.activated—The credit became active after disbursement.
credit.disbursed—The provider confirmed the funds transfer.
credit.restructured—A change to the credit’s terms was approved.
credit.written_off—The credit was written off as an unrecoverable loss.
installment.due_soon—An installment is due within the next 5 days.
compliance.exception_raised—A SARLAFT or UIAF alert, or another regulatory exception, was raised.
compliance.ros_submitted—A suspicious transaction report (ROS) was sent to the UIAF.

Events shared between loans and the classic lifecycle

payment.received, payment.applied and installment.overdue appear in the loans category (loan portfolio, since October 27, 2026) and share their names with the classic credit lifecycle. Subscribe to the key once; the catalog’s payload_example tells you its shape.

Read the live payload_example #

Before writing the handler for an event, ask the catalog for its example: it’s exactly the shape that will reach your receiver.

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))

In the sandbox the catalog is a sample

With a sandbox key, the catalog returns 9 representative events flagged with "sample": true and a generic payload_example. To see the full catalog and the real examples, use a production key in the dev environment. See Test your webhooks.

Payload examples #

wallets.topup.completed (wallet) #

Emitted when a mobile top-up is posted, in the same transaction as the movement.

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"
  }
}

The other wallet events document these fields in resource:

Eventresource fields
wallets.transfer.createdtransaction_id, source_wallet_id, recipient_wallet_id (null if it goes to another bank), amount, currency, rail_reference, is_internal, status; Bre-B (Colombia’s instant payments) adds 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 (loans) #

Emitted when a payment on a credit is recorded (installment, partial prepayment or payoff). Its event_id is deterministic per receipt: retrying your request never produces a second webhook for the same payment.

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"
  }
}
Loans eventresource fields
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 (once per installment)

pqrs.responded (PQRS) #

Emitted when the entity records an official response, whether it comes from the app, the dashboard or the 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:<key> agent:ana.p"
  }
}

pqrs.created also carries issue_type, service_area, status, priority, origin_channel, affiliate_id and deadline; pqrs.status_changed carries from_status, to_status and priority.

Each module’s guide documents its events: wallet, loans, health, administrative and PQRS.

Route by event type #

A single endpoint can receive many events: route on event and let the ones you don’t handle pass through (with a 2xx), so a new event doesn’t trigger retries. The SDKs ship the catalog snapshot for quick checks (EVENT_TYPES in Python, JS and PHP, CompenseWebhooks.EventTypes in .NET and webhook.EventTypes() / webhook.IsKnownEventType() in Go), but they never reject an event they don’t recognize.

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"])
    # any other event: ignore it and still respond 2xx

The complete receiver (signature verification, deduplication and fast response) is in Verify the signature.