docs
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.idnumber

FyPay 中的交易 ID。与 PUT /api/cob/:txid 响应中的 loc.id 值相同。

data.txIdstring

PUT /api/cob/:txid 中使用的 txid。用它查找对应的订单。

data.endToEndIdstring

PIX 的 End to End ID。这是您在 PUT /api/pix/:e2eid/devolucao/:id 中申请退款时使用的标识符。

data.statusstring

交易状态:

  • LIQUIDATED:付款已确认
  • ERROR:处理失败
  • PENDING:仍在处理中
data.paymentobject

data.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);
}

最佳实践

下一步

本页目录