Skip to main content

Visão Geral

O webhook TRANSFER é enviado quando uma transferência PIX iniciada pela sua aplicação é processada. Este evento indica o resultado (sucesso ou falha) de uma chamada ao endpoint /dict/pix.

Quando é enviado

  • Transferência PIX processada com sucesso (LIQUIDATED)
  • Transferência PIX falhou (ERROR)

Estrutura do Payload

{
  "type": "TRANSFER",
  "data": {
    "id": 456,
    "txId": null,
    "pixKey": "[email protected]",
    "status": "LIQUIDATED",
    "payment": {
      "amount": "100.50",
      "currency": "BRL"
    },
    "refunds": [],
    "createdAt": "2024-01-15T10:30:00.000Z",
    "errorCode": null,
    "endToEndId": "E12345678901234567890123456789012",
    "ticketData": {},
    "webhookType": "TRANSFER",
    "debtorAccount": {
      "ispb": null,
      "name": null,
      "issuer": null,
      "number": null,
      "document": null,
      "accountType": null
    },
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "creditDebitType": "DEBIT",
    "creditorAccount": {
      "ispb": "18236120",
      "name": "NU PAGAMENTOS S.A.",
      "issuer": "260",
      "number": "12345-6",
      "document": "123.xxx.xxx-xx",
      "accountType": null
    },
    "localInstrument": "DICT",
    "transactionType": "PIX",
    "remittanceInformation": "Pagamento NF 12345"
  }
}

Campos Importantes

type
string
Sempre "TRANSFER" para PIX enviado.
data.id
number
ID da transação. Mesmo valor retornado no POST /dict/pix.
data.endToEndId
string
End to End ID - identificador único da transação PIX no Banco Central.
data.status
string
Status da transferência:
  • LIQUIDATED: Transferência confirmada (sucesso)
  • ERROR: Falha na transferência
data.payment
object
data.idempotencyKey
string
Chave de idempotência enviada no header x-idempotency-key da requisição original.
data.creditorAccount
object
Dados de quem recebeu (o destinatário).
data.creditDebitType
string
Sempre "DEBIT" para transferências enviadas.
data.errorCode
string
Código de erro quando status === 'ERROR'. Pode ser null em caso de sucesso.
data.remittanceInformation
string
Descrição da transferência (campo description enviado na requisição).

Processando o Webhook

Exemplo Node.js

interface TransferWebhook {
  type: 'TRANSFER';
  data: {
    id: number;
    status: 'LIQUIDATED' | 'ERROR';
    payment: {
      amount: string;
      currency: string;
    };
    endToEndId: string;
    idempotencyKey: string;
    creditorAccount: {
      name: string | null;
      document: string | null;
    };
    errorCode: string | null;
  };
}

async function handleTransfer(webhook: TransferWebhook) {
  const { data } = webhook;

  // Encontrar transferência pelo idempotencyKey
  const transfer = await findTransferByIdempotencyKey(data.idempotencyKey);

  if (!transfer) {
    console.warn(`Transferência não encontrada: ${data.idempotencyKey}`);
    return;
  }

  if (data.status === 'LIQUIDATED') {
    // Sucesso - confirmar transferência
    await updateTransfer(transfer.id, {
      status: 'COMPLETED',
      endToEndId: data.endToEndId,
      completedAt: new Date(),
    });

    // Notificar usuário
    await notifyTransferSuccess({
      transferId: transfer.id,
      amount: parseFloat(data.payment.amount),
      recipient: data.creditorAccount.name,
    });

  } else if (data.status === 'ERROR') {
    // Falha - reverter
    await updateTransfer(transfer.id, {
      status: 'FAILED',
      errorCode: data.errorCode,
    });

    // Notificar usuário
    await notifyTransferFailed({
      transferId: transfer.id,
      errorCode: data.errorCode,
    });

    // Liberar saldo bloqueado
    await releaseBlockedBalance(transfer.id);
  }
}

Exemplo Python

from decimal import Decimal

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

    # Encontrar transferência
    transfer = find_transfer_by_idempotency_key(data['idempotencyKey'])

    if not transfer:
        print(f"Transferência não encontrada: {data['idempotencyKey']}")
        return

    if data['status'] == 'LIQUIDATED':
        # Sucesso
        update_transfer(
            transfer_id=transfer.id,
            status='COMPLETED',
            e2e_id=data['endToEndId']
        )

        notify_transfer_success(
            transfer_id=transfer.id,
            amount=Decimal(data['payment']['amount']),
            recipient=data['creditorAccount'].get('name')
        )

    elif data['status'] == 'ERROR':
        # Falha
        update_transfer(
            transfer_id=transfer.id,
            status='FAILED',
            error_code=data['errorCode']
        )

        notify_transfer_failed(
            transfer_id=transfer.id,
            error_code=data['errorCode']
        )

        # Liberar saldo
        release_blocked_balance(transfer.id)

Correlação com Requisição

Use idempotencyKey para correlacionar o webhook com sua requisição original:
// 1. Criar transferência
const idempotencyKey = crypto.randomUUID();
const transfer = await createTransfer(idempotencyKey, {
  pixKey: '[email protected]',
  amount: 100.50,
});

// 2. Salvar associação
await saveTransfer({
  id: transfer.id,
  idempotencyKey,
  status: 'PENDING',
});

// 3. No webhook TRANSFER
const savedTransfer = await findByIdempotencyKey(webhook.data.idempotencyKey);
// savedTransfer.id corresponde à transferência original

Tratamento de Erros

Códigos de erro comuns:
CódigoDescriçãoAção Recomendada
INSUFFICIENT_BALANCESaldo insuficienteVerificar saldo antes de transferir
INVALID_KEYChave PIX inválidaVerificar chave com usuário
KEY_NOT_FOUNDChave não encontrada no DICTSolicitar chave válida
ACCOUNT_BLOCKEDConta bloqueadaContatar suporte
TIMEOUTTimeout no processamentoTentar novamente
if (data.status === 'ERROR') {
  switch (data.errorCode) {
    case 'INSUFFICIENT_BALANCE':
      // Notificar saldo insuficiente
      await notifyInsufficientBalance(transfer);
      break;

    case 'INVALID_KEY':
    case 'KEY_NOT_FOUND':
      // Solicitar nova chave ao usuário
      await requestNewPixKey(transfer);
      break;

    case 'TIMEOUT':
      // Pode tentar novamente com nova idempotency key
      await retryTransfer(transfer);
      break;

    default:
      // Erro genérico
      await notifyGenericError(transfer, data.errorCode);
  }
}

Fluxo de Saldo

Idempotência

Use data.id para evitar processamento duplicado:
async function handleWebhook(webhook: TransferWebhook) {
  const webhookId = `transfer:${webhook.data.id}`;

  const isProcessed = await redis.sismember('processed', webhookId);
  if (isProcessed) {
    return; // Já processado
  }

  await redis.sadd('processed', webhookId);
  await handleTransfer(webhook);
}

Boas Práticas

Salve o idempotencyKey junto com a transferência para facilitar a correlação no webhook.
Implemente tratamento tanto para LIQUIDATED quanto para ERROR.
Se a transferência falhar, o saldo bloqueado deve ser liberado. Certifique-se de atualizar seu sistema.
Informe o usuário sobre o resultado da transferência, especialmente em caso de falha.

Próximos Passos