Webhooks V2
RECEIVE
概述
当您账户收到的 PIX 状态发生变化时,RECEIVE Webhook 会发送到在 cash_in 事件下以 PIX BACEN 格式登记的 URL。通常表示有人支付了您的应用创建的收款。
发送时机
- 收款(二维码)付款已确认
- 收款失败(
status: ERROR)
负载结构
{
"data": {
"id": 12345,
"txId": "7978c0c97ea847e78e8849634473c1f1",
"pixKey": "7d9f0335-8dcc-4054-9bf9-0dbd61d36906",
"status": "LIQUIDATED",
"payment": {
"amount": "123.45",
"currency": "BRL"
},
"mismatchAmount": null,
"refunds": [],
"createdAt": "2024-01-15T10:30:00.000Z",
"errorCode": null,
"endToEndId": "E12345678202401151030abcdefghijk",
"providerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ticketData": {},
"webhookType": "RECEIVE",
"debtorAccount": {
"ispb": "12345678",
"name": "Carlos Oliveira",
"bankName": "Banco Exemplo S.A.",
"issuer": "000",
"number": "123456",
"document": "12345678909",
"accountType": null
},
"idempotencyKey": null,
"creditDebitType": "CREDIT",
"creditorAccount": {
"ispb": null,
"name": null,
"bankName": null,
"issuer": null,
"number": null,
"document": null,
"accountType": null
},
"localInstrument": "DICT",
"transactionType": "PIX",
"remittanceInformation": "Serviço realizado."
},
"type": "RECEIVE"
}重要字段
typestring始终为 "RECEIVE"。
data.idnumberFyPay 中的交易 ID。与 PUT /api/cob/:txid 响应中的 loc.id 值相同。
data.txIdstringPUT /api/cob/:txid 中使用的 txid。用它查找对应的订单。
data.endToEndIdstringPIX 的 End to End ID。这是您在 PUT /api/pix/:e2eid/devolucao/:id 中申请退款时使用的标识符。
data.statusstring交易状态:
LIQUIDATED:付款已确认ERROR:处理失败PENDING:仍在处理中
data.paymentobjectdata.mismatchAmountstring应收金额与实付金额之差(应收减实付),在两者不同时返回。付款与收款金额一致时为 null。
data.debtorAccountobject付款人数据。
data.creditorAccountobject您的账户。所有字段均为 null。
data.creditDebitTypestring始终为 "CREDIT"。
data.refundsarray始终为空。退款通过单独的 REFUND Webhook 送达。
data.idempotencyKeynull在 RECEIVE 中始终为 null。
data.remittanceInformationstring交易描述(如有)。
处理 Webhook
Node.js 示例
interface ReceiveWebhook {
type: 'RECEIVE';
data: {
id: number;
txId: string | null;
status: 'PENDING' | 'LIQUIDATED' | 'ERROR';
payment: {
amount: string;
currency: 'BRL';
};
mismatchAmount: string | null;
endToEndId: string | null;
errorCode: string | null;
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 未确认:${data.status}`);
return;
}
const order = data.txId ? await findOrderByTxId(data.txId) : null;
if (!order) {
await registerUnmatchedCredit(data);
return;
}
await markOrderAsPaid(order.id, {
amount: data.payment.amount,
divergence: data.mismatchAmount,
endToEndId: data.endToEndId,
payer: data.debtorAccount.name,
});
}Python 示例
from decimal import Decimal
def handle_receive(webhook: dict):
data = webhook['data']
if data['status'] != 'LIQUIDATED':
print(f"PIX 未确认:{data['status']}")
return
amount = Decimal(data['payment']['amount'])
order = find_order_by_txid(data['txId']) if data.get('txId') else None
if order is None:
register_unmatched_credit(data)
return
mark_order_as_paid(
order_id=order.id,
amount=amount,
divergence=data.get('mismatchAmount'),
e2e_id=data['endToEndId'],
payer=data['debtorAccount'].get('name'),
)与收款关联
Webhook 中的 txId 就是您创建收款时使用的 txid:
const txid = 'pedido456a1b2c3d4e5f6g7h8i9j0';
await createCob(txid, { valor: { original: '100.00' } });
await saveOrder({ orderId: 'pedido-456', txId: txid, status: 'PENDING' });在 RECEIVE Webhook 中,按 data.txId 查找订单,并在核销前检查 data.payment.amount 和 data.mismatchAmount。
错误处理
如果 status 为 ERROR,errorCode 字段包含提供商给出的错误码:
if (data.status === 'ERROR') {
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}幂等性
同一个 Webhook 可能会到达多次。请按 type、data.id 和 data.status 去重:
async function handleWebhook(webhook: ReceiveWebhook) {
const key = `receive:${webhook.data.id}:${webhook.data.status}`;
const isNew = await redis.set(`webhook:${key}`, '1', 'NX', 'EX', 60 * 60 * 24 * 7);
if (!isNew) return;
await handleReceive(webhook);
}