Visão Geral
O webhook RECEIVE é enviado quando um PIX é recebido na sua conta. Este evento indica que alguém pagou um QR Code gerado pela sua aplicação ou fez uma transferência direta para sua chave PIX.
Quando é enviado
Pagamento de QR Code (cobrança) confirmado
Transferência direta para chave PIX da conta
Estrutura do 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
Sempre "RECEIVE" para PIX recebido.
ID da transação. Use para idempotência.
Identificador da cobrança (txid do endpoint /cob). Pode ser null para transferências diretas.
End to End ID - identificador único da transação PIX no Banco Central.
Status da transação:
LIQUIDATED: Pagamento confirmado (sucesso)
ERROR: Falha no processamento
Valor recebido. String com 2 casas decimais.
Dados de quem pagou (o pagador/remetente). Código ISPB do banco do pagador.
Nome do banco do pagador.
Código do banco (ex: “260” para Nubank).
Número da conta do pagador.
CPF/CNPJ do pagador (mascarado).
Sempre "CREDIT" para recebimentos.
Lista de devoluções. Vazio para transações sem devolução.
data.remittanceInformation
Descrição da transferência (se informada pelo pagador).
Processando o Webhook
Exemplo 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 não confirmado: ${ data . status } ` );
return ;
}
// Converter valor de string para number
const amount = parseFloat ( data . payment . amount );
// Encontrar pedido pelo txId (se for cobrança)
if ( data . txId ) {
const order = await findOrderByTxId ( data . txId );
if ( order ) {
await markOrderAsPaid ( order . id , {
amount ,
endToEndId: data . endToEndId ,
payer: data . debtorAccount . name ,
});
return ;
}
}
// Recebimento sem cobrança associada
await createGenericCredit ({
amount ,
endToEndId: data . endToEndId ,
payer: data . debtorAccount . name ,
description: data . remittanceInformation ,
});
}
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
# Converter valor
amount = Decimal(data[ 'payment' ][ 'amount' ])
# Processar por txId se existir
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
# Crédito genérico
create_generic_credit(
amount = amount,
e2e_id = data[ 'endToEndId' ],
payer = data[ 'debtorAccount' ].get( 'name' ),
description = data.get( 'remittanceInformation' )
)
Se o PIX foi pago via QR Code gerado pelo endpoint /cob/:txid, o campo txId conterá o identificador:
{
"type" : "RECEIVE" ,
"data" : {
"txId" : "7978c0c97ea847e78e8849634473c1f1" , // Mesmo txid do PUT /cob
// ...
}
}
Use este campo para correlacionar com seus registros internos:
// Criar cobrança
const cobranca = await createCob ( 'meu-txid-123' , { valor: '100.00' });
// Salvar associação
await saveOrder ({
orderId: 'pedido-456' ,
txId: 'meu-txid-123' ,
status: 'PENDING'
});
// No webhook RECEIVE
if ( webhook . data . txId === 'meu-txid-123' ) {
await updateOrder ( 'pedido-456' , { status: 'PAID' });
}
Tratamento de Erros
Se status === 'ERROR', verifique o campo errorCode:
if ( data . status === 'ERROR' ) {
console . error ( `Erro no PIX: ${ data . errorCode } ` );
// Notificar sobre falha
await notifyPaymentError ({
txId: data . txId ,
errorCode: data . errorCode ,
});
}
Idempotência
Use data.id para evitar processamento duplicado:
const PROCESSED_KEY = 'processed_webhooks' ;
async function handleWebhook ( webhook : ReceiveWebhook ) {
const webhookId = `receive: ${ webhook . data . id } ` ;
// Verificar se já processou
const isProcessed = await redis . sismember ( PROCESSED_KEY , webhookId );
if ( isProcessed ) {
console . log ( `Webhook ${ webhookId } já processado` );
return ;
}
// Marcar como processado ANTES de processar
await redis . sadd ( PROCESSED_KEY , webhookId );
// Processar
await handleReceive ( webhook );
}
Boas Práticas
Retorne HTTP 200 imediatamente e processe de forma assíncrona. app . post ( '/webhook' , ( req , res ) => {
res . status ( 200 ). send (); // Responder primeiro
handleWebhook ( req . body ) // Processar depois
. catch ( console . error );
});
Sempre verifique se status === 'LIQUIDATED' antes de creditar.
Se você criou a cobrança via /cob, use o txId para encontrar o pedido correspondente.
Logue informações importantes
Próximos Passos