Skip to main content
Webhooks let your application receive HTTP POST notifications when events happen in your YuvexPay account, such as a payment being confirmed or a withdrawal completing.

Setting up webhooks

  1. Go to Settings > Webhooks in the dashboard.
  2. Click Add webhook and enter your endpoint URL. The URL must use HTTPS.
  3. Select the events you want to receive.
  4. Save the generated secret somewhere safe — it is shown only once and is required to verify incoming requests.
You can register up to two active webhook endpoints per event type per company.

Event types

EventDescription
PAYMENT_CONFIRMEDA card or boleto payment has been authorized by the issuer (held receivable).
PAYMENT_PAIDThe payment has cleared and net funds were credited to your balance.
PAYMENT_EXPIREDThe payment expired before being completed.
PAYMENT_REFUNDEDA refund completed for this payment.
PAYMENT_REFUND_FAILEDA refund attempt failed.
PAYMENT_CHARGEBACKA chargeback was opened or processed.
MED_RECEIVEDA PIX MED (special refund) request was opened by the payer’s bank.
MED_RESOLVEDA PIX MED was resolved (approved or rejected).
WITHDRAWAL_REQUESTEDA withdrawal was created and is being processed.
WITHDRAWAL_SENTA withdrawal was sent successfully.
WITHDRAWAL_FAILEDA withdrawal failed and the funds were returned to your balance.

Webhook payload

Events are delivered as JSON POST requests. A PAYMENT_PAID event carries a payer block describing the account that paid the PIX charge, plus the network endToEndId for bank reconciliation:
{
  "id": "evt_xyz789",
  "type": "PAYMENT_PAID",
  "data": {
    "id": "5d0f8b6e-3a02-4f5b-9e1c-7c6a4a1b8c9d",
    "txId": "PAY3f2a8b9c4e6d1f5a7b3c8d2e9f4a6b1c",
    "status": "PAID",
    "amount": 49.90,
    "paidAt": "2026-06-06T12:00:00.000Z",
    "endToEndId": "E0000000020260606120000000abc1234",
    "payer": {
      "name": "Maria Silva",
      "document": "39053344705",
      "documentType": "CPF",
      "institutionName": "Banco Example S.A.",
      "institutionIspb": "00000000"
    }
  }
}
Every field inside payer is nullable, and the whole block is null for historical records or open-payer charges where no payer data was captured. The payer document (CPF/CNPJ) is returned unredacted when the full value is on file; for some open-payer charges only a masked value (e.g. 75******20) is available. The endToEndId is the PIX-network end-to-end identifier (Banco Central standard), safe to use for reconciliation. A WITHDRAWAL_SENT event carries a recipient block describing the destination account:
{
  "id": "evt_abc123",
  "type": "WITHDRAWAL_SENT",
  "data": {
    "withdrawalId": "9a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
    "txId": "WTH7f3a8b9c4e6d1f5a7b3c8d2e9f4a6b1c",
    "netAmount": 100.00,
    "status": "COMPLETED",
    "endToEndId": "E0000000020260606120500000def5678",
    "recipient": {
      "name": "João Souza",
      "document": "12345678901",
      "institutionName": "Banco Example S.A.",
      "institutionIspb": "00000000",
      "branch": "0001",
      "account": "123456"
    }
  }
}
Like payer, every recipient field is nullable and the block is null when no recipient data was captured. These blocks are provider-agnostic: the shape is identical regardless of which PIX provider settled the transaction. The fields inside data otherwise depend on the event type and mirror the corresponding resource in the API reference.

Request headers

Each webhook delivery includes these headers:
HeaderDescription
Content-Typeapplication/json
User-AgentYuvexPay-Webhook/1.0
X-Webhook-EventThe event type (e.g., PAYMENT_PAID).
X-Webhook-Delivery-IdA unique UUID for this delivery. Use it to deduplicate retries.
X-Webhook-AttemptAttempt number, starting at 1. Increments on retries.
X-Webhook-TimestampUnix timestamp (seconds) when the request was signed.
X-Webhook-Signaturev1=<hex-encoded HMAC-SHA256>. See Verifying signatures.
X-Webhook-Signature-LegacyDeprecated. Body-only HMAC kept for backwards compatibility. Plan to remove from your verifier.

Verifying signatures

YuvexPay signs each delivery with the webhook secret you saved when creating the endpoint. The recommended v1 algorithm signs both a timestamp and the raw request body, which prevents both tampering and replay attacks.
expected = "v1=" + hex(HMAC_SHA256(secret, timestamp + "." + raw_body))
To verify a request:
  1. Read X-Webhook-Timestamp and X-Webhook-Signature from the headers.
  2. Compute the expected signature using your stored secret and the raw, unparsed request body.
  3. Compare in constant time. Reject if they don’t match.
  4. Reject any request whose timestamp is more than 300 seconds off the current time. This is the recommended replay window and matches what YuvexPay applies internally.
import crypto from "crypto";

const SKEW_SECONDS = 300;

function verifyWebhook(rawBody, headers, secret) {
  const ts = headers["x-webhook-timestamp"];
  const sig = headers["x-webhook-signature"];
  if (!ts || !sig) return false;

  const drift = Math.abs(Math.floor(Date.now() / 1000) - parseInt(ts, 10));
  if (drift > SKEW_SECONDS) return false;

  const expected = "v1=" + crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body.toString("utf8");
  if (!verifyWebhook(rawBody, req.headers, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(rawBody);
  console.log(`Received ${event.type}:`, event.data);

  res.status(200).send("OK");
});
Always verify the v1 signature and the timestamp drift before processing any event. Never trust the payload without verification.

Delivery semantics

YuvexPay aims to deliver every event at least once. A few characteristics worth designing around:
  • At-least-once delivery. The same event may arrive more than once. Treat X-Webhook-Delivery-Id as the deduplication key. If you’ve processed a delivery id before, return 200 and skip the side effects.
  • Order is not guaranteed. Deliveries are dispatched in parallel; under retry, a PAYMENT_REFUNDED may arrive before its prior PAYMENT_PAID. Always reconcile against the resource state in data.status, not against the order of arrival.
  • Synchronous timeout: 10 seconds. Return 2xx within 10 seconds. Process work asynchronously if you need longer.
  • Redirects are not followed. Your endpoint must respond directly.

Retry behavior

If your endpoint returns a non-2xx status code or fails to respond, YuvexPay retries with exponential backoff. The default schedule is:
AttemptDelay
1st retry5 seconds
2nd retry10 seconds
3rd retry20 seconds
You can configure up to 10 retries per endpoint. After all retries are exhausted, the delivery is marked FAILED. Failed deliveries can be inspected and manually replayed from the dashboard. A manual replay reuses the same X-Webhook-Delivery-Id.

Best practices

  • Verify before trusting. Always validate the X-Webhook-Signature and the X-Webhook-Timestamp skew.
  • Return 200 quickly. Acknowledge receipt synchronously and do business logic in a background job.
  • Deduplicate by delivery id. The same event can be delivered more than once — X-Webhook-Delivery-Id is stable across retries and replays.
  • Don’t rely on order. Use the payload’s status to decide what to do, not the sequence of arrivals.
  • Use HTTPS. Webhook endpoints must use HTTPS.
  • Don’t log the secret. Treat the webhook secret like an API key.