docs
Webhooks V2

RECEIVE

Visión General

El webhook RECEIVE se envía a las URLs registradas en el evento cash_in con el formato PIX BACEN, cuando un PIX recibido en su cuenta cambia de estado. Normalmente indica que alguien pagó un cobro creado por su aplicación.

Cuándo se envía

  • Pago de cobro (QR Code) confirmado
  • Falla en la recepción (status: ERROR)

Estructura del 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": "Servicio realizado."
  },
  "type": "RECEIVE"
}

Campos Importantes

typestring

Siempre "RECEIVE".

data.idnumber

ID de la transacción en FyPay. Es el mismo valor de loc.id en la respuesta de PUT /api/cob/:txid.

data.txIdstring

El txid usado en PUT /api/cob/:txid. Úselo para encontrar el pedido correspondiente.

data.endToEndIdstring

End to End ID del PIX. Es el identificador que usted usa para solicitar una devolución en PUT /api/pix/:e2eid/devolucao/:id.

data.statusstring

Estado de la transacción:

  • LIQUIDATED: pago confirmado
  • ERROR: falla en el procesamiento
  • PENDING: aún en procesamiento
data.paymentobject

data.mismatchAmountstring

Diferencia entre el valor cobrado y el valor pagado (cobrado menos pagado), cuando son distintos. null cuando el pago coincide con el cobro.

data.debtorAccountobject

Datos de quien pagó.

data.creditorAccountobject

Su cuenta. Todos los campos vienen null.

data.creditDebitTypestring

Siempre "CREDIT".

data.refundsarray

Siempre vacío. Las devoluciones llegan en webhooks REFUND propios.

data.idempotencyKeynull

Siempre null en RECEIVE.

data.remittanceInformationstring

Descripción de la transacción, cuando exista.

Procesando el Webhook

Ejemplo 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 no 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,
  });
}

Ejemplo Python

from decimal import Decimal

def handle_receive(webhook: dict):
    data = webhook['data']

    if data['status'] != 'LIQUIDATED':
        print(f"PIX no 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'),
    )

Correlación con el Cobro

El txId del webhook es el mismo txid que usted usó para crear el cobro:

const txid = 'pedido456a1b2c3d4e5f6g7h8i9j0';
await createCob(txid, { valor: { original: '100.00' } });

await saveOrder({ orderId: 'pedido-456', txId: txid, status: 'PENDING' });

En el webhook RECEIVE, busque el pedido por data.txId y revise data.payment.amount y data.mismatchAmount antes de darlo por pagado.

Manejo de Errores

Si status es ERROR, el campo errorCode trae el código informado por el proveedor:

if (data.status === 'ERROR') {
  await notifyPaymentError({
    txId: data.txId,
    errorCode: data.errorCode,
  });
}

Idempotencia

El mismo webhook puede llegar más de una vez. Deduplique por type, data.id y 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);
}

Buenas Prácticas

Próximos Pasos

En esta página