RECEIVE
Visão Geral
O webhook RECEIVE é enviado para as URLs cadastradas no evento cash_in com o formato PIX BACEN, quando um PIX recebido na sua conta muda de estado. Normalmente indica que alguém pagou uma cobrança criada pela sua aplicação.
Quando é enviado
- Pagamento de cobrança (QR Code) confirmado
- Falha no recebimento (
status: ERROR)
Estrutura do 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": "Serviço realizado."
},
"type": "RECEIVE"
}Campos Importantes
typestringSempre "RECEIVE".
data.idnumberID da transação na FyPay. É o mesmo valor de loc.id na resposta de PUT /api/cob/:txid.
data.txIdstringO txid usado em PUT /api/cob/:txid. Use para encontrar o pedido correspondente.
data.endToEndIdstringEnd to End ID do PIX. É o identificador que você usa para pedir uma devolução em PUT /api/pix/:e2eid/devolucao/:id.
data.statusstringStatus da transação:
LIQUIDATED: pagamento confirmadoERROR: falha no processamentoPENDING: ainda em processamento
data.paymentobjectdata.mismatchAmountstringDiferença entre o valor cobrado e o valor pago (cobrado menos pago), quando forem diferentes. null quando o pagamento bate com a cobrança.
data.debtorAccountobjectDados de quem pagou.
data.creditorAccountobjectSua conta. Todos os campos vêm null.
data.creditDebitTypestringSempre "CREDIT".
data.refundsarraySempre vazio. Devoluções chegam em webhooks REFUND próprios.
data.idempotencyKeynullSempre null em RECEIVE.
data.remittanceInformationstringDescrição da transação, quando houver.
Processando o Webhook
Exemplo 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 não 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,
});
}Exemplo Python
from decimal import Decimal
def handle_receive(webhook: dict):
data = webhook['data']
if data['status'] != 'LIQUIDATED':
print(f"PIX não 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'),
)Correlação com a Cobrança
O txId do webhook é o mesmo txid que você usou para criar a cobrança:
const txid = 'pedido456a1b2c3d4e5f6g7h8i9j0';
await createCob(txid, { valor: { original: '100.00' } });
await saveOrder({ orderId: 'pedido-456', txId: txid, status: 'PENDING' });No webhook RECEIVE, busque o pedido por data.txId e confira data.payment.amount e data.mismatchAmount antes de dar baixa.
Tratamento de Erros
Se status for ERROR, o campo errorCode traz o código informado pelo provedor:
if (data.status === 'ERROR') {
await notifyPaymentError({
txId: data.txId,
errorCode: data.errorCode,
});
}Idempotência
O mesmo webhook pode chegar mais de uma vez. Deduplique por type, data.id e 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);
}