Skip to content

Webhooks ​

Events are delivered by HTTP POST to your active endpoints.

Event catalog ​

typewhen
payment.succeededpayment confirmed
payment.settledpayment settled — planned, not fired yet (there is no automatic settlement confirmation today; see Settlement)
payment.cancelledpayment cancelled
payment.failedpayment declined

Internally, payment.captured is delivered publicly as payment.succeeded. payment.authorized does not produce a webhook.

Envelope ​

json
{
  "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, and holiday (with country and name). An empty array means the date landed directly, with no adjustments.
  • There is no "D+N" field. Use estimated_date; the reason explains the path. Do not add days yourself.
  • Outside the calendar's coverage, the settlement object 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.succeeded carries the forecast; the platform does not emit an event today confirming that the credit actually happened on estimated_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):

js
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 2xx response.
  • 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_lettered and 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 each id only 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:

json
{
  "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.succeeded for 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.succeeded as 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.