Webhooks V2
概述
什么是 V2 Webhooks?
V2 Webhooks 是当 PIX 交易中发生重要事件时发送到您应用程序的通知。V2 格式使用 {type, data} 信封结构,使处理更加简便,并提供每个事件的更多详细信息。
要接收 V2 Webhooks,您的账户必须将 Webhook 版本配置为 V2。请参阅如何激活。
基本结构
所有 V2 Webhooks 遵循以下结构:
{
"type": "RECEIVE" | "TRANSFER" | "REFUND",
"data": {
// 事件特定数据
}
}事件类型
| 类型 | 描述 | V1 等价 |
|---|---|---|
RECEIVE | 收到 PIX(Cash-In) | CashIn |
TRANSFER | 发送 PIX(Cash-Out) | CashOut |
REFUND | 退款(入账或出账) | CashInReversal / CashOutReversal |
完整数据结构
interface WebhookV2Data {
// 标识符
id: number; // 交易 ID
txId: string | null; // 收款标识符(txid)
endToEndId: string | null; // PIX 交易的端到端 ID
// PIX 密钥
pixKey: string | null; // 使用的 PIX 密钥
// 状态
status: 'PENDING' | 'LIQUIDATED' | 'REFUNDED' | 'ERROR';
// 付款
payment: {
amount: string; // 金额(带 2 位小数的字符串)
currency: string; // 货币(BRL)
};
// 退款
refunds: RefundInfo[]; // 退款列表(如无则为空)
// 日期
createdAt: string; // 创建日期(ISO 8601)
// 错误
errorCode: string | null; // 错误码(如有)
// 操作类型
webhookType: 'RECEIVE' | 'TRANSFER' | 'REFUND';
creditDebitType: 'CREDIT' | 'DEBIT';
transactionType: 'PIX';
localInstrument: 'DICT';
// 账户
debtorAccount: AccountInfo; // 付款方/发送方
creditorAccount: AccountInfo; // 收款方/接收方
// 幂等性
idempotencyKey: string | null;
// 附加数据
ticketData: object;
remittanceInformation: string | null; // 交易描述
}
interface AccountInfo {
ispb: string | null; // 银行 ISPB 代码
name: string | null; // 银行名称
issuer: string | null; // 银行代码
number: string | null; // 账号
document: string | null; // CPF/CNPJ(已脱敏)
accountType: string | null; // 账户类型
}
interface RefundInfo {
status: 'PENDING' | 'LIQUIDATED' | 'ERROR';
payment: {
amount: number; // 退款金额(数值类型!)
currency: string;
};
errorCode: string | null;
eventDate: string; // 退款日期
endToEndId: string | null; // 退款 E2E ID
information: string | null; // 退款描述
}V1 与 V2 的区别
| 方面 | V1 | V2 |
|---|---|---|
| 格式 | 字段在根层级 | 信封结构 {type, data} |
| 事件类型 | event: "CashIn" | type: "RECEIVE" |
| 方面 | V1 | V2 |
|---|---|---|
| PIX 成功 | CONFIRMED | LIQUIDATED |
| 退款成功 | CONFIRMED | REFUNDED |
| 错误 | ERROR | ERROR |
| 方面 | V1 | V2 |
|---|---|---|
| 字段 | counterpart | debtorAccount / creditorAccount |
| 银行 | bank.bankName | name |
| ISPB | bank.bankISPB | ispb |
| 方面 | V1 | V2 |
|---|---|---|
| 类型 | number | string |
| 格式 | 100.00 | "100.00" |
账户映射
收到 PIX(RECEIVE)
debtorAccount = 付款方(交易对手)
creditorAccount = 您的账户(收款方)
creditDebitType = CREDIT发送 PIX(TRANSFER)
debtorAccount = 您的账户(付款方)
creditorAccount = 收款方(交易对手)
creditDebitType = DEBIT收款退款(REFUND - CashInReversal)
debtorAccount = 您的账户(退款方)
creditorAccount = 退款接收方(交易对手)
creditDebitType = DEBIT转账退款(REFUND - CashOutReversal)
debtorAccount = 退款方(交易对手)
creditorAccount = 您的账户(退款接收方)
creditDebitType = CREDIT端点配置
要求
- 必须使用 HTTPS URL
- 最大超时时间:10 秒
- 期望响应:HTTP 2xx
身份认证
Webhooks 使用 Basic Auth 发送:
Authorization: Basic base64(username:password)在控制面板中配置凭据或联系技术支持。
处理器示例
import express from 'express';
const app = express();
app.use(express.json());
interface WebhookV2 {
type: 'RECEIVE' | 'TRANSFER' | 'REFUND';
data: WebhookV2Data;
}
// 用于幂等性的集合
const processedIds = new Set<number>();
app.post('/webhooks/pix', (req, res) => {
const webhook: WebhookV2 = req.body;
// 立即响应
res.status(200).json({ acknowledged: true });
// 检查幂等性
if (processedIds.has(webhook.data.id)) {
console.log(`Webhook ${webhook.data.id} already processed`);
return;
}
processedIds.add(webhook.data.id);
// 按类型处理
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}`);
// 在系统中入账
}
}
function handleTransfer(data: WebhookV2Data) {
if (data.status === 'LIQUIDATED') {
console.log(`PIX sent: ${data.endToEndId}`);
// 确认转账
} else if (data.status === 'ERROR') {
console.log(`PIX failed: ${data.errorCode}`);
// 回滚操作
}
}
function handleRefund(data: WebhookV2Data) {
if (data.status === 'REFUNDED') {
const refund = data.refunds[0];
console.log(`Refund: R$ ${refund.payment.amount}`);
// 处理退款
}
}重试机制
如果您的端点未在 10 秒内返回 HTTP 2xx 响应:
| 尝试次数 | 间隔 | 累计 |
|---|---|---|
| 第 1 次 | 立即 | 0 分钟 |
| 第 2 次 | 5 分钟 | 5 分钟 |
| 第 3 次 | 5 分钟 | 10 分钟 |
| 第 4 次 | 15 分钟 | 25 分钟 |
4 次尝试失败后,Webhook 将不再自动重发。
请实现定期轮询作为后备方案,以确保不会遗漏任何交易。