RECEIVE
Descripción general
El webhook RECEIVE se envia cuando un PIX es recibido en su cuenta. Este evento indica que alguien pago un QR Code generado por su aplicación o realizo una transferencia directa a su clave PIX.
Cuando se envia
- Pago de QR Code (cobro) confirmado
- Transferencia directa a la clave PIX de la cuenta
Estructura del Payload
{
"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"
}
}Campos Importantes
typestringSiempre "RECEIVE" para PIX recibidos.
data.idnumberID de la transacción. Use para idempotencia.
data.txIdstringIdentificador del cobro (txid del endpoint /cob). Puede ser null para transferencias directas.
data.endToEndIdstringEnd to End ID - identificador único de la transacción PIX en el Banco Central.
data.statusstringEstado de la transacción:
LIQUIDATED: Pago confirmado (exito)ERROR: Falla en el procesamiento
data.paymentobjectdata.debtorAccountobjectDatos de quien pago (el pagador/emisor).
data.creditDebitTypestringSiempre "CREDIT" para recepciones.
data.refundsarrayLista de devoluciones. Vacia para transacciones sin devoluciones.
data.remittanceInformationstringDescripción de la transferencia (si fue proporcionada por el pagador).
Procesamiento del Webhook
Ejemplo en 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 no confirmado: ${data.status}`);
return;
}
// Convertir valor de string a numero
const amount = parseFloat(data.payment.amount);
// Buscar orden por txId (si es un cobro)
if (data.txId) {
const order = await findOrderByTxId(data.txId);
if (order) {
await markOrderAsPaid(order.id, {
amount,
endToEndId: data.endToEndId,
payer: data.debtorAccount.name,
});
return;
}
}
// Recepcion sin cobro asociado
await createGenericCredit({
amount,
endToEndId: data.endToEndId,
payer: data.debtorAccount.name,
description: data.remittanceInformation,
});
}Ejemplo en 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
# Convertir valor
amount = Decimal(data['payment']['amount'])
# Procesar por txId si existe
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
# Credito generico
create_generic_credit(
amount=amount,
e2e_id=data['endToEndId'],
payer=data['debtorAccount'].get('name'),
description=data.get('remittanceInformation')
)Correlacion con el Cobro
Si el PIX fue pagado via un QR Code generado por el endpoint /cob/:txid, el campo txId contendra el identificador:
{
"type": "RECEIVE",
"data": {
"txId": "7978c0c97ea847e78e8849634473c1f1",
// ...
}
}Use este campo para correlacionar con sus registros internos:
// Crear cobro
const charge = await createCob('my-txid-123', { valor: '100.00' });
// Guardar asociacion
await saveOrder({
orderId: 'order-456',
txId: 'my-txid-123',
status: 'PENDING'
});
// En el webhook RECEIVE
if (webhook.data.txId === 'my-txid-123') {
await updateOrder('order-456', { status: 'PAID' });
}Manejo de Errores
Si status === 'ERROR', verifique el campo errorCode:
if (data.status === 'ERROR') {
console.error(`Error de PIX: ${data.errorCode}`);
// Notificar sobre la falla
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}Idempotencia
Use data.id para evitar el procesamiento duplicado:
const PROCESSED_KEY = 'processed_webhooks';
async function handleWebhook(webhook: ReceiveWebhook) {
const webhookId = `receive:${webhook.data.id}`;
// Verificar si ya fue procesado
const isProcessed = await redis.sismember(PROCESSED_KEY, webhookId);
if (isProcessed) {
console.log(`Webhook ${webhookId} ya procesado`);
return;
}
// Marcar como procesado ANTES de procesar
await redis.sadd(PROCESSED_KEY, webhookId);
// Procesar
await handleReceive(webhook);
}