Webhooks
Webhooks
Compense notifies you over HTTP when something changes in your entity: a credit decision, a top-up, a loan payment, an appointment or a PQRS. At-least-once delivery, signed with HMAC-SHA256 and self-managed through the API.
On this page
A webhook is a POST request that Compense sends to a URL you own every time a business event
happens in your entity (organization): a credit was approved, a top-up was confirmed, a loan payment
was applied, an appointment was scheduled, a PQRS (complaints and requests) was answered. Instead of
querying the API every so often (polling), your system finds out within seconds, and only when
there is something new.
Everything is managed through the API, with your key: you register your endpoints, choose the events, rotate the signing secret, send test events, review the delivery history and retry failed deliveries. You don’t need to log in to the dashboard.
You only receive events for what you have enabled
Webhooks are a gateway capability and don’t depend on a subscribed module. But you only receive events from the modules your entity has enabled (credits, subsidies, wallet, loans, health…), because only those modules generate them.
How a delivery works #
- A change happens in your entity (through the API, from the dashboard or from the affiliate’s app). The event is written in the same transaction as the change: there is never a webhook for something that wasn’t recorded.
- Compense sends a
POSTto each of your active endpoints subscribed to that event, with the signed JSON body. - Your receiver verifies the signature over the raw body, deduplicates by
X-Webhook-Id, responds2xxin under 10 seconds and processes in the background. - If your receiver doesn’t respond
2xx(or doesn’t respond in time), Compense retries the same delivery with exponential backoff. If it runs out of retries, the delivery becomesabandonedand you can retry it manually.
sequenceDiagram
autonumber
participant M as Compense engine
participant D as Webhook dispatcher
participant R as Your receiver
participant Q as Your queue / DB
M->>D: Event (same transaction as the change)
D->>R: POST /compense/webhooks (JSON body + X-Webhook-Signature)
R->>R: HMAC-SHA256(secret, raw body) == signature?
alt Invalid signature or timestamp outside the window
R-->>D: 401
else Valid signature
R->>Q: X-Webhook-Id already processed? if not, enqueue
R-->>D: 200 (in under 10 s)
end
opt Non-2xx response, timeout or network error
Note over D: Retries: 30 s, 2 min, 8 min, 32 min, ~2 h
D->>R: POST (same delivery, same X-Webhook-Id)
Note over D: No success after max_retries → abandoned
endAt-least-once delivery #
Compense guarantees that each event is delivered at least once, not exactly once. An
automatic retry can arrive even if your first response was in fact processed (for example, if your
200 got lost on the network). That’s why your receiver must be idempotent:
- Store the
X-Webhook-Id(theevent_id) of every processed event and discard duplicates. - Don’t assume ordering between events. Use
occurred_atand the state carried inresourceto decide, and when in doubt, fetch the resource from the API.
The payload envelope #
Namespaced events (credits.*, subsidies.*, monetary_allowance.*, affiliates.*,
wallets.*, loans, health, administrative and pqrs.*) share the same envelope:
{
"event": "credits.decision",
"version": "1",
"tenant_id": "horizonte",
"occurred_at": "2026-10-27T15:20:00Z",
"resource": { "…": "event-specific fields" }
}| Field | What it is |
|---|---|
event | Event key in the catalog. Matches the X-Webhook-Event header. |
version | Major version of the resource schema. A new optional field doesn’t change it. |
tenant_id | Your organization’s code. |
occurred_at | When the event happened (ISO-8601, UTC). |
resource | The event data. Amounts as decimal strings ("20000.00"), never as numbers. |
The classic credit lifecycle events (general category: application.*, credit.*,
compliance.*, installment.due_soon) predate the envelope and carry their fields at the top
level of the body, without resource. The SDKs give you the full body in payload so you can read
them. Details and examples in Event catalog.
Headers on every delivery #
| Header | Value |
|---|---|
X-Webhook-Signature | sha256=<hex>: HMAC-SHA256 of the raw body with your secret, in lowercase hexadecimal |
X-Webhook-Event | Event type (credits.decision, test.ping, …) |
X-Webhook-Id | The delivery’s event_id: your deduplication key |
X-Webhook-Timestamp | Unix seconds at send time (not part of the signature) |
Content-Type | application/json |
User-Agent | Compense-Webhooks/1.0 |
How to verify them step by step, with and without the SDK, in Verify the signature.
Endpoint lifecycle #
An endpoint goes through four states. You control the first two; Compense sets the other two based on how your receiver responds.
stateDiagram-v2
[*] --> active: create
active --> paused: PATCH active=false
paused --> active: PATCH active=true
active --> failing: 10 or more deliveries in 24 h, less than half successful
active --> disabled: repeated 410 Gone
failing --> disabled: repeated 410 Gone
disabled --> [*]: create another endpoint| State | Meaning |
|---|---|
active | Receives events. |
paused | You paused it with active: false. It doesn’t receive events and keeps its history. |
failing | With 10 or more deliveries in 24 hours, less than 50% were successful. Check your receiver. |
disabled | Compense disabled it (e.g. after 3 410 Gone responses). It can’t be reactivated through the API: create another one or contact support. |
What you need before you start #
- An API key with the
webhooks:readscope (read) andwebhooks:manage(create, edit, rotate, test and retry). - A public HTTPS URL for your receiver.
localhost, private IPs and internal domains are not accepted; to develop on your machine see Test your webhooks. - A secure place for the signing secret (
whsec_...): it’s shown only once.
In this section #
version, amounts as strings and payload examples.
Step 2 · essential
Verify the signature
Complete receivers in Flask, FastAPI, Express, Laravel, ASP.NET Core and Go, plus manual verification without an SDK.
Operations
Rotate the secret
Change the secret without losing events by accepting both during the rollout.
Operations
Retries and deliveries
Automatic backoff, delivery history, details with attempts, and reprocessing every failed delivery.
Development
Test your webhooks
Test events, compense trigger, compense listen to localhost and the webhooks sandbox.