RECEIVE
Visión General
El webhook RECEIVE se envía a las URLs registradas en el evento cash_in con el formato PIX BACEN, cuando un PIX recibido en su cuenta cambia de estado. Normalmente indica que alguien pagó un cobro creado por su aplicación.
Cuándo se envía
- Pago de cobro (QR Code) confirmado
- Falla en la recepción (
status: ERROR)
Estructura del Payload
{
"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": "Servicio realizado."
},
"type": "RECEIVE"
}Campos Importantes
typestringSiempre "RECEIVE".
data.idnumberID de la transacción en FyPay. Es el mismo valor de loc.id en la respuesta de PUT /api/cob/:txid.
data.txIdstringEl txid usado en PUT /api/cob/:txid. Úselo para encontrar el pedido correspondiente.
data.endToEndIdstringEnd to End ID del PIX. Es el identificador que usted usa para solicitar una devolución en PUT /api/pix/:e2eid/devolucao/:id.
data.statusstringEstado de la transacción:
LIQUIDATED: pago confirmadoERROR: falla en el procesamientoPENDING: aún en procesamiento
data.paymentobjectdata.mismatchAmountstringDiferencia entre el valor cobrado y el valor pagado (cobrado menos pagado), cuando son distintos. null cuando el pago coincide con el cobro.
data.debtorAccountobjectDatos de quien pagó.
data.creditorAccountobjectSu cuenta. Todos los campos vienen null.
data.creditDebitTypestringSiempre "CREDIT".
data.refundsarraySiempre vacío. Las devoluciones llegan en webhooks REFUND propios.
data.idempotencyKeynullSiempre null en RECEIVE.
data.remittanceInformationstringDescripción de la transacción, cuando exista.
Procesando el Webhook
Ejemplo 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 no confirmado: ${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,
});
}Ejemplo Python
from decimal import Decimal
def handle_receive(webhook: dict):
data = webhook['data']
if data['status'] != 'LIQUIDATED':
print(f"PIX no confirmado: {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'),
)Correlación con el Cobro
El txId del webhook es el mismo txid que usted usó para crear el cobro:
const txid = 'pedido456a1b2c3d4e5f6g7h8i9j0';
await createCob(txid, { valor: { original: '100.00' } });
await saveOrder({ orderId: 'pedido-456', txId: txid, status: 'PENDING' });En el webhook RECEIVE, busque el pedido por data.txId y revise data.payment.amount y data.mismatchAmount antes de darlo por pagado.
Manejo de Errores
Si status es ERROR, el campo errorCode trae el código informado por el proveedor:
if (data.status === 'ERROR') {
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}Idempotencia
El mismo webhook puede llegar más de una vez. Deduplique por type, data.id y 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);
}