RECEIVE
Overview
The RECEIVE webhook is sent to the URLs registered for the cash_in event with the PIX BACEN format when a PIX received in your account changes state. It usually means someone paid a charge created by your application.
When it is sent
- Charge (QR Code) payment confirmed
- Receipt failure (
status: ERROR)
Payload Structure
{
"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"
}Key Fields
typestringAlways "RECEIVE".
data.idnumberTransaction ID at FyPay. Same value as loc.id in the PUT /api/cob/:txid response.
data.txIdstringThe txid used in PUT /api/cob/:txid. Use it to find the matching order.
data.endToEndIdstringPIX End to End ID. It is the identifier you use to request a refund at PUT /api/pix/:e2eid/devolucao/:id.
data.statusstringTransaction status:
LIQUIDATED: payment confirmedERROR: processing failurePENDING: still being processed
data.paymentobjectdata.mismatchAmountstringDifference between the charged amount and the paid amount (charged minus paid), when they differ. null when the payment matches the charge.
data.debtorAccountobjectPayer data.
data.creditorAccountobjectYour account. All fields come as null.
data.creditDebitTypestringAlways "CREDIT".
data.refundsarrayAlways empty. Refunds arrive in their own REFUND webhooks.
data.idempotencyKeynullAlways null in RECEIVE.
data.remittanceInformationstringTransaction description, when present.
Processing the Webhook
Node.js Example
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 not confirmed: ${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 Example
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'])
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'),
)Correlation with the Charge
The webhook txId is the same txid you used to create the charge:
const txid = 'pedido456a1b2c3d4e5f6g7h8i9j0';
await createCob(txid, { valor: { original: '100.00' } });
await saveOrder({ orderId: 'pedido-456', txId: txid, status: 'PENDING' });In the RECEIVE webhook, look up the order by data.txId and check data.payment.amount and data.mismatchAmount before settling it.
Error Handling
If status is ERROR, the errorCode field carries the code reported by the provider:
if (data.status === 'ERROR') {
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}Idempotency
The same webhook may arrive more than once. Deduplicate by type, data.id and 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);
}