Skip to main content

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

type
string
Sempre "RECEIVE" para PIX recebido.
data.id
number
ID da transação. Use para idempotência.
data.txId
string
Identificador da cobrança (txid do endpoint /cob). Pode ser null para transferências diretas.
data.endToEndId
string
End to End ID - identificador único da transação PIX no Banco Central.
data.status
string
Status da transação:
  • LIQUIDATED: Pagamento confirmado (sucesso)
  • ERROR: Falha no processamento
data.payment
object
data.debtorAccount
object
Dados de quem pagou (o pagador/remetente).
data.creditDebitType
string
Sempre "CREDIT" para recebimentos.
data.refunds
array
Lista de devoluções. Vazio para transações sem devolução.
data.remittanceInformation
string
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')
    )

Correlação com Cobrança

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.
console.log({
  event: 'PIX_RECEIVED',
  id: data.id,
  txId: data.txId,
  amount: data.payment.amount,
  e2eId: data.endToEndId,
  payer: data.debtorAccount.name,
});

Próximos Passos