Callbacks and webhooks
Signed payment and payout notices — the right way to reconcile without polling the API.
WeetPay notifies your system in two ways, which can be used together:
| Transaction callback | Dashboard webhook | |
|---|---|---|
| Where to set it | callbackUrl field on each charge or payout in the API | Webhooks in the dashboard, one URL per event |
| Format | the compatible API payload (amounts in reais) | envelope { id, event, createdAt, data } (amounts in cents) |
| Signed with | the API client secret | the webhook secret (whsec_…), shown in the dashboard |
| Events | charge paid; payout result | charge created, paid, refunded; payout completed, failed |
Both: POST with Content-Type: application/json, signature in the X-WeetPay-Signature header and automatic retries on failure.
Charge paid callback
Sent to the callbackUrl from Create PIX charge when the PIX is paid:
{
"requestNumber": "order-12345",
"statusTransaction": "PAID_OUT",
"idTransaction": "cmh1x2y3z0001ab12cd34ef56",
"typeTransaction": "PIX",
"value": 49.9,
"debtorName": "MARIA DA SILVA",
"debtorDocument": "12345678909",
"date": "2026-10-30T13:03:40.000Z",
"endToEnd": "E18236120202610301303s0123456789"
}| Field | Description |
|---|---|
requestNumber | the identifier you sent on creation |
statusTransaction | always PAID_OUT (only payments trigger callbacks) |
idTransaction | WeetPay charge ID |
value | paid amount, in reais (number) |
debtorName, debtorDocument | who paid, as reported by the bank |
date | payment date/time (ISO 8601, UTC) |
endToEnd | PIX End-to-End ID |
Payout callback
Sent to the callbackUrl from Send PIX when the payout finishes:
{
"id": "cmh1x9k2m0004ab12xy98zt76",
"status": "sucess",
"date": "2026-10-30T14:10:05.000Z",
"message": "",
"value": 150,
"endToEnd": "E00416968202610301410abcdEFGH1234"
}status is "sucess" (spelled this way for compatibility with the original gateway API) or "not paid". A payout that is not paid has its amount and fee refunded to your balance automatically; the reason is in message.
Dashboard webhooks
Register a URL for each event you want under Webhooks in the dashboard. The body comes in an envelope:
{
"id": "cmh1xa0b10007ab12qq11ww22",
"event": "TRANSACTION_PAID",
"createdAt": "2026-10-30T13:03:41.000Z",
"data": {
"id": "cmh1x2y3z0001ab12cd34ef56",
"externalId": "order-12345",
"status": "PAID",
"amount": 4990,
"fee": 99,
"netAmount": 4891,
"description": "Basic t-shirt",
"pixCopiaECola": "00020101021226850014br.gov.bcb.pix…",
"endToEndId": "E18236120202610301303s0123456789",
"payer": { "name": "MARIA DA SILVA", "document": "12345678909" },
"expiresAt": "2026-10-30T14:02:11.000Z",
"paidAt": "2026-10-30T13:03:40.000Z",
"createdAt": "2026-10-30T13:02:11.000Z"
}
}event | When | data |
|---|---|---|
TRANSACTION_CREATED | charge created | charge |
TRANSACTION_PAID | charge paid | charge |
TRANSACTION_REFUNDED | charge refunded / disputed | charge + refund { id, kind, amount, reason } |
WITHDRAWAL_COMPLETED | payout paid | payout |
WITHDRAWAL_FAILED | payout rejected or failed (refunded) | payout |
On charges, externalId is your requestNumber; amount, fee and netAmount are in cents. Payouts carry id, transactionId, status (PAID, FAILED, REJECTED), amount, fee, pixKeyType, pixKey, endToEndId, reason, paidAt and createdAt.
Headers
| Header | Value |
|---|---|
X-WeetPay-Signature | t=<unix>,v1=<hex> — see below |
X-WeetPay-Event | event (TRANSACTION_PAID, WITHDRAWAL_COMPLETED…) |
X-WeetPay-Delivery | delivery ID — the same across all attempts; use it to drop duplicates |
User-Agent | WeetPay-Webhooks/1.0 |
Validating the signature
v1 is the hex HMAC-SHA256 of "<t>.<raw body>", keyed with the client secret (transaction callbacks) or the webhook secret (dashboard webhooks). Compute it over the body exactly as received, before any parsing, compare in constant time and reject notices whose t is too old (e.g. over 5 minutes).
import { createHmac, timingSafeEqual } from 'node:crypto';
export function validWeetPaySignature(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Delivery and retries
- Reply with any 2xx within 10 seconds. Any other status, a timeout or a connection error counts as a failure.
- Failures are retried after 1, 5, 15, 60, 180, 360, 720 and 1440 minutes (up to 9 attempts, about 2 days). After that the delivery is marked as failed in the history; you can resend it manually from the dashboard.
- The same notice may arrive more than once: key on
idTransaction/id(orX-WeetPay-Delivery) and make processing idempotent. - The URL must be public http(s). Redirects are not followed and internal network addresses (localhost, 10.x, 192.168.x…) are refused.
- Reply fast and process later (queue): the notice does not wait for your system to finish the order.
Do not rely on the notice body alone to release a high-value order: validate the signature or confirm with Get transaction.