Webhooks V2
Overview
What are V2 Webhooks?
V2 Webhooks are notifications sent to your application when important events occur in your PIX transactions. The V2 format uses a {type, data} envelope structure that makes processing easier and provides more detail about each event.
To receive V2 webhooks, your account must have the webhook version configured to V2. See how to activate.
Base Structure
All V2 webhooks follow this structure:
{
"type": "RECEIVE" | "TRANSFER" | "REFUND",
"data": {
// Event-specific data
}
}Event Types
| Type | Description | V1 Equivalent |
|---|---|---|
RECEIVE | PIX received (Cash-In) | CashIn |
TRANSFER | PIX sent (Cash-Out) | CashOut |
REFUND | Refund (In or Out) | CashInReversal / CashOutReversal |
Complete Data Structure
interface WebhookV2Data {
// Identifiers
id: number; // Transaction ID
txId: string | null; // Charge identifier (txid)
endToEndId: string | null; // End to End ID of the PIX transaction
// PIX Key
pixKey: string | null; // PIX key used
// Status
status: 'PENDING' | 'LIQUIDATED' | 'REFUNDED' | 'ERROR';
// Payment
payment: {
amount: string; // Amount (string with 2 decimals)
currency: string; // Currency (BRL)
};
// Refunds
refunds: RefundInfo[]; // List of refunds (empty if none)
// Dates
createdAt: string; // Creation date (ISO 8601)
// Error
errorCode: string | null; // Error code (if any)
// Operation type
webhookType: 'RECEIVE' | 'TRANSFER' | 'REFUND';
creditDebitType: 'CREDIT' | 'DEBIT';
transactionType: 'PIX';
localInstrument: 'DICT';
// Accounts
debtorAccount: AccountInfo; // Payer/Sender
creditorAccount: AccountInfo; // Receiver/Recipient
// Idempotency
idempotencyKey: string | null;
// Additional data
ticketData: object;
remittanceInformation: string | null; // Transaction description
}
interface AccountInfo {
ispb: string | null; // Bank ISPB code
name: string | null; // Bank name
issuer: string | null; // Bank code
number: string | null; // Account number
document: string | null; // CPF/CNPJ (masked)
accountType: string | null; // Account type
}
interface RefundInfo {
status: 'PENDING' | 'LIQUIDATED' | 'ERROR';
payment: {
amount: number; // Refund amount (number!)
currency: string;
};
errorCode: string | null;
eventDate: string; // Refund date
endToEndId: string | null; // Refund E2E ID
information: string | null; // Refund description
}V1 vs V2 Differences
| Aspect | V1 | V2 |
|---|---|---|
| Format | Fields at root | Envelope {type, data} |
| Event type | event: "CashIn" | type: "RECEIVE" |
| Aspect | V1 | V2 |
|---|---|---|
| PIX success | CONFIRMED | LIQUIDATED |
| Refund success | CONFIRMED | REFUNDED |
| Error | ERROR | ERROR |
| Aspect | V1 | V2 |
|---|---|---|
| Field | counterpart | debtorAccount / creditorAccount |
| Bank | bank.bankName | name |
| ISPB | bank.bankISPB | ispb |
| Aspect | V1 | V2 |
|---|---|---|
| Type | number | string |
| Format | 100.00 | "100.00" |
Account Mapping
PIX Received (RECEIVE)
debtorAccount = Who paid (counterparty)
creditorAccount = Your account (receiver)
creditDebitType = CREDITPIX Sent (TRANSFER)
debtorAccount = Your account (payer)
creditorAccount = Who received (counterparty)
creditDebitType = DEBITReceipt Refund (REFUND - CashInReversal)
debtorAccount = Your account (refunding)
creditorAccount = Who will receive back (counterparty)
creditDebitType = DEBITTransfer Refund (REFUND - CashOutReversal)
debtorAccount = Who is refunding (counterparty)
creditorAccount = Your account (receiving back)
creditDebitType = CREDITEndpoint Configuration
Requirements
- HTTPS URL required
- Maximum timeout: 10 seconds
- Expected response: HTTP 2xx
Authentication
Webhooks are sent with Basic Auth:
Authorization: Basic base64(username:password)Configure credentials in the dashboard or contact support.
Handler Example
import express from 'express';
const app = express();
app.use(express.json());
interface WebhookV2 {
type: 'RECEIVE' | 'TRANSFER' | 'REFUND';
data: WebhookV2Data;
}
// Set for idempotency
const processedIds = new Set<number>();
app.post('/webhooks/pix', (req, res) => {
const webhook: WebhookV2 = req.body;
// Respond quickly
res.status(200).json({ acknowledged: true });
// Check idempotency
if (processedIds.has(webhook.data.id)) {
console.log(`Webhook ${webhook.data.id} already processed`);
return;
}
processedIds.add(webhook.data.id);
// Process by type
switch (webhook.type) {
case 'RECEIVE':
handleReceive(webhook.data);
break;
case 'TRANSFER':
handleTransfer(webhook.data);
break;
case 'REFUND':
handleRefund(webhook.data);
break;
}
});
function handleReceive(data: WebhookV2Data) {
if (data.status === 'LIQUIDATED') {
const amount = parseFloat(data.payment.amount);
console.log(`PIX received: R$ ${amount}`);
// Credit in the system
}
}
function handleTransfer(data: WebhookV2Data) {
if (data.status === 'LIQUIDATED') {
console.log(`PIX sent: ${data.endToEndId}`);
// Confirm transfer
} else if (data.status === 'ERROR') {
console.log(`PIX failed: ${data.errorCode}`);
// Reverse operation
}
}
function handleRefund(data: WebhookV2Data) {
if (data.status === 'REFUNDED') {
const refund = data.refunds[0];
console.log(`Refund: R$ ${refund.payment.amount}`);
// Process refund
}
}Retries
If your endpoint does not respond with HTTP 2xx within 10 seconds:
| Attempt | Interval | Cumulative |
|---|---|---|
| 1st | Immediate | 0 min |
| 2nd | 5 minutes | 5 min |
| 3rd | 5 minutes | 10 min |
| 4th | 15 minutes | 25 min |
After 4 unsuccessful attempts, the webhook will no longer be automatically resent.
Implement periodic polling as a fallback to ensure no transactions are missed.