Webhooks
Events are delivered by HTTP POST to your active endpoints.
Event catalog
| type | when |
|---|---|
payment.succeeded | payment confirmed |
payment.settled | payment settled — planned, not fired yet (there is no automatic settlement confirmation today; see Settlement) |
payment.cancelled | payment cancelled |
payment.failed | payment declined |
Internally, payment.captured is delivered publicly as payment.succeeded. payment.authorized does not produce a webhook.
Envelope
{
"id": "evt_9f8c...", "type": "payment.succeeded", "created": 1789990000,
"data": {
"payment": {
"id": "pay_9f8c...",
"amount": 510000,
"currency": "BRL",
"gross_amount": 510000,
"gross_currency": "BRL",
"fee_amount": 25000,
"fee_currency": "BRL",
"net_amount": 95000,
"net_currency": "USD",
"payment_method": "pix",
"status": "succeeded",
"reference": "pedido-123",
"failure_code": null,
"fx": {
"rate": 5.1,
"base": "USD",
"quote": "BRL",
"locked_at": 1800000000,
"valid_until": null,
"rate_history_id": 123
},
"settlement": {
"estimated_date": "2026-09-08",
"reason": [
{ "type": "cutoff", "note": "..." },
{ "type": "weekend", "date": "2026-09-05" },
{ "type": "holiday", "date": "2026-09-07", "country": "BR", "name": "..." }
]
}
}
}
}The public payload never includes spot_rate, cobertura_centibps, source, median_3_stablecoin, percent_centibps, or rule percentages.
Settlement (settlement)
Present only on payment.succeeded. It tells you when the money should land and why that date — so you can close your books without recomputing the banking calendar (BR/Febraban + US/Fedwire), the 3 p.m. cutoff (America/Sao_Paulo), and the weekend rollover yourself.
estimated_date(YYYY-MM-DD, America/Sao_Paulo) is an ESTIMATE, not a guarantee. It reflects the calendar rules in force at confirmation time.reason[]is the chronological trail of what moved the date:cutoff(paid after 3 p.m.),weekend, andholiday(withcountryandname). An empty array means the date landed directly, with no adjustments.- There is no "D+N" field. Use
estimated_date; thereasonexplains the path. Do not add days yourself. - Outside the calendar's coverage, the
settlementobject is omitted — we never send a date without coverage. Treat the absence as "no forecast", not as an error. - There is no settlement-confirmation event.
payment.succeededcarries the forecast; the platform does not emit an event today confirming that the credit actually happened onestimated_date. Do not assume automatic confirmation — if you need to reconcile the real credit, do it from your bank statement.
Signature
Each delivery carries the header:
Pagooz-Signature: t=1789990000,v1=<hex>v1 is the HMAC-SHA256, in hex, of "{t}.{body}" (the timestamp, a dot, and the raw request body), using the endpoint's secret. Verify and reject deliveries whose t is more than 5 minutes from now (replay tolerance).
Working example (Node):
const crypto = require("crypto");
function verifyPagoozSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min
const expected = crypto.createHmac("sha256", secret)
.update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Delivery, retry, and consumer idempotency
- A delivery only succeeds with a
2xxresponse. - A failure triggers 5 attempts with backoff: immediate, 1 minute, 5 minutes, 30 minutes, and 2 hours.
- After the 5th failure, the delivery becomes
dead_letteredand visible in the cockpit. - At-least-once delivery. The same event may arrive more than once (retry, re-notification, redelivery). Your consumer must be idempotent by
event.id— process eachidonly once.
Configuration in the cockpit
One URL per tenant per environment (sandbox and live). The cockpit reads the session's global mode and saves the URL for that environment. The signing secret is shown once, at creation/rotation. The webhook_endpoints table is not used in this version; it is reserved for multiple destinations in the future.
Reconciliation and re-notification
Manual resend in the cockpit
The merchant can resend a failed delivery from the cockpit. The resent payload keeps the same event_id; the new attempt is recorded in webhook_deliveries with origin = "manual".
Delivery history
The cockpit shows the latest deliveries per environment. Each object:
{
"id": "whd_9f8c...", "event_id": "evt_9f8c...",
"attempt": 3, "status": "failed", "http_status": 503,
"response_body": "...",
"origin": "auto", "created": 1789990000, "next_attempt_at": 1789990300
}It lets you diagnose on your own why a notification did not arrive.