Webhooks V2
RECEIVE
概述
RECEIVE Webhook 在您的账户收到 PIX 时发送。此事件表示有人支付了您应用程序生成的二维码,或直接向您的 PIX 密钥发起了转账。
发送时机
- 二维码(收款)付款确认
- 直接向账户 PIX 密钥的转账
负载结构
{
"type": "RECEIVE",
"data": {
"id": 123,
"txId": "7978c0c97ea847e78e8849634473c1f1",
"pixKey": "7d9f0335-8dcc-4054-9bf9-0dbd61d36906",
"status": "LIQUIDATED",
"payment": {
"amount": "100.00",
"currency": "BRL"
},
"refunds": [],
"createdAt": "2024-01-15T10:30:00.000Z",
"errorCode": null,
"endToEndId": "E12345678901234567890123456789012",
"ticketData": {},
"webhookType": "RECEIVE",
"debtorAccount": {
"ispb": "18236120",
"name": "NU PAGAMENTOS S.A.",
"issuer": "260",
"number": "12345-6",
"document": "123.xxx.xxx-xx",
"accountType": null
},
"idempotencyKey": null,
"creditDebitType": "CREDIT",
"creditorAccount": {
"ispb": null,
"name": null,
"issuer": null,
"number": null,
"document": null,
"accountType": null
},
"localInstrument": "DICT",
"transactionType": "PIX",
"remittanceInformation": "Pagamento pedido #12345"
}
}重要字段
typestring收到 PIX 时始终为 "RECEIVE"。
data.idnumber交易 ID。用于幂等性处理。
data.txIdstring收款标识符(来自 /cob 端点的 txid)。直接转账时可为 null。
data.endToEndIdstring端到端 ID - PIX 交易在中央银行的唯一标识符。
data.statusstring交易状态:
LIQUIDATED:付款已确认(成功)ERROR:处理失败
data.paymentobjectdata.debtorAccountobject付款方(发送方)的数据。
data.creditDebitTypestring收款时始终为 "CREDIT"。
data.refundsarray退款列表。无退款的交易为空数组。
data.remittanceInformationstring转账描述(如付款方提供)。
处理 Webhook
Node.js 示例
interface ReceiveWebhook {
type: 'RECEIVE';
data: {
id: number;
txId: string | null;
status: 'LIQUIDATED' | 'ERROR';
payment: {
amount: string;
currency: string;
};
endToEndId: string;
debtorAccount: {
name: string | null;
document: string | null;
};
remittanceInformation: string | null;
};
}
async function handleReceive(webhook: ReceiveWebhook) {
const { data } = webhook;
if (data.status !== 'LIQUIDATED') {
console.log(`PIX not confirmed: ${data.status}`);
return;
}
// 将金额从字符串转换为数值
const amount = parseFloat(data.payment.amount);
// 通过 txId 查找订单(如果是收款)
if (data.txId) {
const order = await findOrderByTxId(data.txId);
if (order) {
await markOrderAsPaid(order.id, {
amount,
endToEndId: data.endToEndId,
payer: data.debtorAccount.name,
});
return;
}
}
// 无关联收款的入账
await createGenericCredit({
amount,
endToEndId: data.endToEndId,
payer: data.debtorAccount.name,
description: data.remittanceInformation,
});
}Python 示例
from decimal import Decimal
def handle_receive(webhook: dict):
data = webhook['data']
if data['status'] != 'LIQUIDATED':
print(f"PIX not confirmed: {data['status']}")
return
# 转换金额
amount = Decimal(data['payment']['amount'])
# 如果存在 txId,按 txId 处理
if data.get('txId'):
order = find_order_by_txid(data['txId'])
if order:
mark_order_as_paid(
order_id=order.id,
amount=amount,
e2e_id=data['endToEndId'],
payer=data['debtorAccount'].get('name')
)
return
# 通用入账
create_generic_credit(
amount=amount,
e2e_id=data['endToEndId'],
payer=data['debtorAccount'].get('name'),
description=data.get('remittanceInformation')
)与收款的关联
如果 PIX 是通过 /cob/:txid 端点生成的二维码支付的,txId 字段将包含该标识符:
{
"type": "RECEIVE",
"data": {
"txId": "7978c0c97ea847e78e8849634473c1f1",
// ...
}
}使用此字段与您的内部记录进行关联:
// 创建收款
const charge = await createCob('my-txid-123', { valor: '100.00' });
// 保存关联
await saveOrder({
orderId: 'order-456',
txId: 'my-txid-123',
status: 'PENDING'
});
// 在 RECEIVE Webhook 中
if (webhook.data.txId === 'my-txid-123') {
await updateOrder('order-456', { status: 'PAID' });
}错误处理
如果 status === 'ERROR',请检查 errorCode 字段:
if (data.status === 'ERROR') {
console.error(`PIX error: ${data.errorCode}`);
// 通知付款失败
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}幂等性
使用 data.id 避免重复处理:
const PROCESSED_KEY = 'processed_webhooks';
async function handleWebhook(webhook: ReceiveWebhook) {
const webhookId = `receive:${webhook.data.id}`;
// 检查是否已处理
const isProcessed = await redis.sismember(PROCESSED_KEY, webhookId);
if (isProcessed) {
console.log(`Webhook ${webhookId} already processed`);
return;
}
// 在处理前标记为已处理
await redis.sadd(PROCESSED_KEY, webhookId);
// 处理
await handleReceive(webhook);
}