Webhooks — concepts
Webhooks are the mechanism Max Pay uses to notify events to the integrator's ERP in near-real time. Without webhooks, the only alternative is polling, which is inefficient and latent.
This page covers the conceptual model. To implement (create the endpoint, verify the HMAC signature, send a test delivery), see Webhooks → Getting started. For the operational reference (endpoints, pause/resume, rotate the secret), see Webhooks → Configuration. For the event catalog, see Events.
Model
If the integrator's endpoint does not respond 2xx within 10 seconds, Max Pay retries with exponential backoff.
Delivery
Each event is sent as a POST to the configured URL with:
Headers
| Header | Description |
|---|---|
X-MaxPay-Signature | HMAC signature. See verification |
X-MaxPay-Event | Event type. E.g. receivable.created |
X-MaxPay-Delivery-Id | UUID of this delivery (changes between retries of the same event) |
Body
{
"id": "evt_b3f8a1c2d4e5f6789012345678901234",
"type": "receivable.created",
"createdAt": "2026-05-15T14:30:05-03:00",
"data": {
"receivable": { "id": 1234, "amount": 45000, "..." : "..." }
}
}
Events are immutable. If the underlying data changes (e.g. the receivable is updated), a new event is emitted (receivable.updated); the original is not modified.
Signature verification
Every delivery carries the X-MaxPay-Signature signature:
X-MaxPay-Signature: t=1715812800,v1=5257a869e7ecebeda...
Where:
t: Unix timestamp at signing time (same value asX-MaxPay-Timestamp).v1: HMAC-SHA256 computed over<timestamp>.<body>using the webhooksecretas the key.
The integrator must recompute the HMAC with their secret and compare in constant time. It is also good practice to reject deliveries with an old timestamp (typically > 5 minutes) to prevent replay attacks.
For a complete implementation with example code, see Webhooks → Getting started.
Without verification, any attacker with your public URL can inject fake events into your ERP. Verification is the difference between a robust integration and an attack vector.
Retries
If the endpoint responds with anything other than 2xx, Max Pay retries on this schedule:
| Attempt | Delay from previous |
|---|---|
| 1 | (immediate) |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
Total: ~14 hours from the original event. After that window, the delivery is discarded and the webhook is marked as FAILED. To diagnose and resend failed deliveries, see Delivery diagnostics.
Event deduplication
Because of the "at-least-once" retry model, the integrator may receive the same event more than once. The event.id is unique and immutable for each event, and stays constant across retries of the same event. The standard strategy is to persist processed event.ids and discard (responding 200 OK) any that are already on the list.
For the implementation pattern, see Webhooks → Getting started.
Order and concurrency
Events close together in time may arrive out of order due to internal concurrency. The integrator must not assume a sequence between distinct events (e.g. receivable.created may arrive after receivable.paid if retries of the former lagged).
For rollback handling: a reversed transaction is notified as a movement.created with operationType: ROLLBACK referencing the original movement. The integrator's matching logic must contemplate this case.