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.
Bank transfer (TED) — confirmation by review
A charge paid by bank transfer is not confirmed by a system: the payer uploads the proof at checkout and Poolvo reviews the receipt before treating the charge as paid. That changes what you should expect as an integrator:
- The
payment.succeededfor a transfer arrives after the review, not at the moment the payer says they paid. Hours can pass in between, and the review happens during business hours. Do not treat the delay as a failure, and do not reissue the charge. - While awaiting review, the payment exists and does not count as paid: it is not added to your period close and it generates no webhook.
- A rejection at review (a receipt that does not check out) arrives as
payment.failed. - Once the payment is confirmed, the webhook is the same
payment.succeededas PIX and card, with the same fields. You need no per-method handling.
payment_methods in POST /v1/payment_links accepts pix and card. A bank-transfer charge is created through the quote flow (POST /v1/quotes with payment_method: "ted", then accepting the quote), and the Payment object returns payment_method: "ted" normally.
Settlement is confirmed outside the API
The settlement forecast comes in payment.succeeded (see Webhooks → Settlement). Confirmation that the credit actually landed is recorded by Poolvo through a manual review, and is not exposed in the API today: there is no event, and no settlement status field on the Payment object. payment.settled remains planned. If you need to reconcile the real credit, use your bank statement — do not wait for a notification from us.