docs
Webhooks V2

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

typestring

Sempre "RECEIVE".

data.idnumber

ID da transação na FyPay. É o mesmo valor de loc.id na resposta de PUT /api/cob/:txid.

data.txIdstring

O txid usado em PUT /api/cob/:txid. Use para encontrar o pedido correspondente.

data.endToEndIdstring

End to End ID do PIX. É o identificador que você usa para pedir uma devolução em PUT /api/pix/:e2eid/devolucao/:id.

data.statusstring

Status da transação:

  • LIQUIDATED: pagamento confirmado
  • ERROR: falha no processamento
  • PENDING: ainda em processamento
data.paymentobject

data.mismatchAmountstring

Diferenç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.debtorAccountobject

Dados de quem pagou.

data.creditorAccountobject

Sua conta. Todos os campos vêm null.

data.creditDebitTypestring

Sempre "CREDIT".

data.refundsarray

Sempre vazio. Devoluções chegam em webhooks REFUND próprios.

data.idempotencyKeynull

Sempre null em RECEIVE.

data.remittanceInformationstring

Descriçã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);
}

Boas Práticas

Próximos Passos

Nesta página