Fire Bankingdocs
Guias de Integração

PIX Cash-Out (Pagamento)

Visão Geral

O endpoint PIX Cash-Out permite que você realize pagamentos PIX instantâneos para qualquer chave PIX válida (CPF, CNPJ, telefone, email ou chave aleatória). O pagamento é processado em tempo real e o valor é debitado da sua conta imediatamente.

Para pagamentos via QR Code PIX (escaneamento ou copia-e-cola), utilize o endpoint dedicado Cash-Out via QR Code. Este endpoint é exclusivo para pagamentos por chave PIX.

Este endpoint requer um token Bearer válido. Verifique a documentação de autenticação para mais detalhes.

Características

  • Pagamentos instantâneos 24/7
  • Suporte a todos os tipos de chave PIX (CPF, CNPJ, telefone, email, chave aleatória)
  • Validação automática de dados do destinatário
  • Verificação de saldo e limites automática
  • Identificação única por externalId (idempotência)
  • Descrição personalizável para o destinatário
  • Confirmação assíncrona via webhook (status PENDING → CONFIRMED ou ERROR)

Endpoint

POST /api/pix/cash-out

Realiza um pagamento PIX.

Headers Obrigatórios

Authorization: Bearer {token}
Content-Type: application/json

Request Body

{
  "value": 250.50,
  "details": {
    "key": "12345678901",
    "keyType": "DOCUMENT",
    "name": "Ana Costa",
    "document": "12345678901"
  },
  "externalId": "PAYMENT-987654-20240119",
  "description": "Pagamento de fornecedor"
}

Request

curl -X POST https://api.public.firebanking.com.br/api/pix/cash-out \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "value": 250.50,
    "details": {
      "key": "12345678901",
      "keyType": "DOCUMENT",
      "name": "Ana Costa",
      "document": "12345678901"
    },
    "externalId": "PAYMENT-987654-20240119",
    "description": "Pagamento de fornecedor"
  }'

Veja a seção Formatos de Chave PIX abaixo para os formatos exatos aceitos para cada tipo de chave.

Response (201 Created)

{
  "transactionId": "9876",
  "externalId": "PAYMENT-987654-20240119",
  "status": "PENDING",
  "generateTime": "2024-01-19T15:45:00.000Z"
}

Parâmetros da Requisição

valuenumberobrigatorio

Valor do pagamento em reais (BRL). Deve ter no máximo 2 casas decimais.

Mínimo: 0.01

Exemplo: 250.50

detailsobjectobrigatorio

Informações da chave PIX de destino.

details.keystringobrigatorio

Chave PIX de destino. O formato deve corresponder ao keyType informado.

Formatos aceitos:

  • CPF: 12345678901 (11 dígitos, apenas números)
  • CNPJ: 12345678000199 (14 dígitos, apenas números)
  • Email: usuario@exemplo.com
  • Telefone: 11999999999 (10 ou 11 dígitos — DDD + número, sem DDI +55)
  • Chave aleatória: UUID formato xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Veja a tabela completa em Formatos de Chave PIX.

details.keyTypestringobrigatorio

Tipo da chave PIX. O valor é case-insensitive (convertido para maiúsculas automaticamente).

Valores aceitos:

  • DOCUMENT — CPF ou CNPJ (a distinção é feita automaticamente pela contagem de dígitos)
  • EMAIL — Endereço de email
  • PHONE — Número de telefone (DDD + número)
  • RANDOM — Chave aleatória (EVP/UUID)

Por que é obrigatório? A API não realiza consulta DICT para inferir o tipo da chave. A identificação deve vir na requisição para que o sistema normalize o formato correto antes de enviar ao banco liquidante (ex: adicionar +55 em chaves PHONE, converter DOCUMENT para CPF/CNPJ).

Exemplo: "DOCUMENT"

details.namestringobrigatorio

Nome do titular da chave PIX de destino.

Máximo: 100 caracteres

Uso: Campo informativo — utilizado para registros de extrato e identificação do destinatário. Não é validado pelo banco liquidante contra o cadastro da chave PIX. Enviar um nome diferente do titular não causa erro na transação.

Exemplo: "Ana Costa"

details.documentstringobrigatorio

CPF ou CNPJ do titular da chave PIX de destino (apenas números). Validado algoritmicamente (dígitos verificadores).

CPF: 11 dígitos | CNPJ: 14 dígitos

Obrigatório para todos os tipos de chave — inclusive EMAIL, PHONE e RANDOM. Este campo é enviado ao banco liquidante para verificação na DICT (Diretório de Identificadores de Contas Transacionais). Se o documento não corresponder ao dono da chave PIX, o banco retornará um erro e a transação será automaticamente cancelada (veja Erros de Negócio).

Cenários por keyType:

  • DOCUMENT: o details.document deve ser idêntico ao details.key (ambos são o CPF/CNPJ). Se divergirem, o banco rejeitará com TAX_ID_MISMATCH.
  • EMAIL, PHONE, RANDOM: não é possível derivar o documento a partir da chave. O integrador precisa solicitar o CPF/CNPJ ao usuário final e enviar neste campo.

Dica de UX: Sempre exiba um campo de CPF/CNPJ na interface quando o tipo de chave não for DOCUMENT.

Exemplo: "12345678901"

externalIdstringobrigatorio

Identificador único externo da transação. Deve ser único por conta — se repetido, a API retornará erro DUPLICATE_EXTERNAL_ID.

Máximo: 50 caracteres

Recomendação: Use um formato que garanta unicidade, como PAY-{timestamp}-{uuid}

Exemplo: "PAYMENT-987654-20240119"

descriptionstring

Descrição do pagamento que aparecerá no extrato do destinatário.

Máximo: 140 caracteres

Padrão: Vazio

Exemplo: "Pagamento de fornecedor - Nota Fiscal 12345"

Estrutura da Resposta

transactionIdstringsempre presente

ID interno da transação gerada pela Avista.

Exemplo: "9876"

externalIdstringsempre presente

ID externo fornecido na requisição (mesmo valor do input).

Exemplo: "PAYMENT-987654-20240119"

statusstringsempre presente

Status atual da transação.

Valores possíveis:

  • PENDING: Pagamento em processamento
  • CONFIRMED: Pagamento confirmado e finalizado
  • ERROR: Erro no processamento

Exemplo: "PENDING"

Nota: A maioria dos pagamentos PIX é confirmada em poucos segundos

generateTimestringsempre presente

Data e hora de criação do pagamento (ISO 8601 UTC).

Exemplo: "2024-01-19T15:45:00.000Z"

Exemplos de Implementação

Node.js / TypeScript

import axios from 'axios';

interface CashOutRequest {
  value: number;
  details: {
    key: string;
    keyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
    name: string;
    document: string;
  };
  externalId: string;
  description?: string;
}

interface CashOutResponse {
  transactionId: string;
  externalId: string;
  status: 'PENDING' | 'CONFIRMED' | 'ERROR';
  generateTime: string;
}

async function sendPixPayment(
  token: string,
  recipientKey: string,
  recipientKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM',
  recipientName: string,
  recipientDocument: string,
  amount: number,
  description?: string
): Promise<CashOutResponse> {
  const payload: CashOutRequest = {
    value: amount,
    details: {
      key: recipientKey,
      keyType: recipientKeyType,
      name: recipientName,
      document: recipientDocument
    },
    externalId: `PAY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
    description: description || `Pagamento PIX de R$ ${amount.toFixed(2)}`
  };

  try {
    const response = await axios.post<CashOutResponse>(
      'https://api.public.firebanking.com.br/api/pix/cash-out',
      payload,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('Pagamento PIX iniciado com sucesso!');
    console.log(`ID da Transação: ${response.data.transactionId}`);
    console.log(`Status: ${response.data.status}`);
    console.log(`Valor: R$ ${amount.toFixed(2)}`);
    console.log(`Destinatário: ${recipientName}`);

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const errorData = error.response?.data;
      console.error('Erro ao realizar pagamento:', errorData);

      // Tratar erros específicos
      if (error.response?.status === 400) {
        if (errorData?.message?.includes('saldo insuficiente')) {
          throw new Error('Saldo insuficiente para realizar o pagamento');
        }
        throw new Error('Dados inválidos: ' + errorData?.message);
      }

      throw new Error(errorData?.message || 'Erro ao realizar pagamento PIX');
    }
    throw error;
  }
}

// Uso - Pagamento por CPF
sendPixPayment(
  'seu_token_aqui',
  '12345678901',
  'DOCUMENT',
  'Ana Costa',
  '12345678901',
  250.50,
  'Pagamento de fornecedor'
);

// Uso - Pagamento por Email
sendPixPayment(
  'seu_token_aqui',
  'ana.costa@email.com',
  'EMAIL',
  'Ana Costa',
  '12345678901',
  100.00,
  'Reembolso'
);

// Uso - Pagamento por Telefone (DDD + número, sem DDI +55)
sendPixPayment(
  'seu_token_aqui',
  '11999999999',
  'PHONE',
  'Ana Costa',
  '12345678901',
  50.00
);

Python

import requests
from datetime import datetime
from typing import Dict, Optional
import uuid

def send_pix_payment(
    token: str,
    recipient_key: str,
    recipient_key_type: str,
    recipient_name: str,
    recipient_document: str,
    amount: float,
    description: Optional[str] = None
) -> Dict:
    """
    Envia um pagamento PIX

    Args:
        token: Token Bearer válido
        recipient_key: Chave PIX do destinatário
        recipient_key_type: Tipo da chave (DOCUMENT, EMAIL, PHONE, RANDOM)
        recipient_name: Nome do destinatário
        recipient_document: CPF ou CNPJ do destinatário
        amount: Valor em reais
        description: Descrição do pagamento (opcional)

    Returns:
        Dados do pagamento iniciado
    """
    url = 'https://api.public.firebanking.com.br/api/pix/cash-out'

    payload = {
        'value': round(amount, 2),
        'details': {
            'key': recipient_key,
            'keyType': recipient_key_type,
            'name': recipient_name,
            'document': recipient_document
        },
        'externalId': f'PAY-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:8]}',
        'description': description or f'Pagamento PIX de R$ {amount:.2f}'
    }

    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }

    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()

        data = response.json()

        print('Pagamento PIX iniciado com sucesso!')
        print(f"ID da Transação: {data['transactionId']}")
        print(f"Status: {data['status']}")
        print(f"Valor: R$ {amount:.2f}")
        print(f"Destinatário: {recipient_name}")

        return data

    except requests.exceptions.HTTPError as e:
        error_data = e.response.json() if e.response else {}

        # Tratar erros específicos
        if e.response.status_code == 400:
            if 'saldo insuficiente' in error_data.get('message', '').lower():
                raise Exception('Saldo insuficiente para realizar o pagamento')
            raise Exception(f"Dados inválidos: {error_data.get('message')}")

        raise Exception(f"Erro ao realizar pagamento: {error_data.get('message', str(e))}")

# Uso
token = 'seu_token_aqui'

# Pagamento por CPF
payment = send_pix_payment(
    token=token,
    recipient_key='12345678901',
    recipient_key_type='DOCUMENT',
    recipient_name='Ana Costa',
    recipient_document='12345678901',
    amount=250.50,
    description='Pagamento de fornecedor'
)

PHP

<?php

function sendPixPayment(
    string $token,
    string $recipientKey,
    string $recipientKeyType,
    string $recipientName,
    string $recipientDocument,
    float $amount,
    ?string $description = null
): array {
    $url = 'https://api.public.firebanking.com.br/api/pix/cash-out';

    $payload = [
        'value' => round($amount, 2),
        'details' => [
            'key' => $recipientKey,
            'keyType' => $recipientKeyType,
            'name' => $recipientName,
            'document' => $recipientDocument
        ],
        'externalId' => 'PAY-' . time() . '-' . bin2hex(random_bytes(4)),
        'description' => $description ?? "Pagamento PIX de R$ " . number_format($amount, 2, ',', '.')
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 201) {
        $errorData = json_decode($response, true);
        $errorMessage = $errorData['message'] ?? "HTTP $httpCode";

        if ($httpCode === 400 && stripos($errorMessage, 'saldo insuficiente') !== false) {
            throw new Exception('Saldo insuficiente para realizar o pagamento');
        }

        throw new Exception("Erro ao realizar pagamento: $errorMessage");
    }

    $data = json_decode($response, true);

    echo "Pagamento PIX iniciado com sucesso!" . PHP_EOL;
    echo "ID da Transação: {$data['transactionId']}" . PHP_EOL;
    echo "Status: {$data['status']}" . PHP_EOL;
    echo "Valor: R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;
    echo "Destinatário: $recipientName" . PHP_EOL;

    return $data;
}

// Uso
$token = 'seu_token_aqui';
$payment = sendPixPayment(
    $token,
    '12345678901',
    'DOCUMENT',
    'Ana Costa',
    '12345678901',
    250.50,
    'Pagamento de fornecedor'
);

Casos de Uso

1. Folha de Pagamento

class PayrollProcessor {
  constructor(private token: string) {}

  async processPayroll(employees: Employee[]) {
    const results = {
      successful: [],
      failed: []
    };

    for (const employee of employees) {
      try {
        // Verificar saldo antes de cada pagamento
        const balance = await getBalance(this.token);

        if (balance.netBalance < employee.salary) {
          throw new Error('Saldo insuficiente');
        }

        // Realizar pagamento
        const payment = await sendPixPayment(
          this.token,
          employee.pixKey,
          employee.pixKeyType,
          employee.fullName,
          employee.document,
          employee.salary,
          `Salário ${new Date().toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })}`
        );

        results.successful.push({
          employee: employee.fullName,
          amount: employee.salary,
          transactionId: payment.transactionId
        });

        // Aguardar 1 segundo entre pagamentos
        await this.sleep(1000);

      } catch (error) {
        results.failed.push({
          employee: employee.fullName,
          error: error.message
        });
      }
    }

    return results;
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Uso
interface Employee {
  fullName: string;
  document: string;
  pixKey: string;
  pixKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
  salary: number;
}

const payroll = new PayrollProcessor('seu_token_aqui');
const employees: Employee[] = [
  {
    fullName: 'Pedro Santos',
    document: '12345678901',
    pixKey: '12345678901',
    pixKeyType: 'DOCUMENT',
    salary: 3500.00
  },
  // ... mais funcionários
];

const results = await payroll.processPayroll(employees);
console.log(`Pagamentos bem-sucedidos: ${results.successful.length}`);
console.log(`Pagamentos com erro: ${results.failed.length}`);

2. Marketplace - Repasse para Vendedores

class MarketplacePayouts:
    """Processa repasses para vendedores de marketplace"""

    def __init__(self, token: str):
        self.token = token

    def process_seller_payouts(self, sales_data: list) -> dict:
        """Processa repasses baseados em vendas"""
        results = {'successful': [], 'failed': []}

        # Agrupar vendas por vendedor
        seller_totals = self.group_sales_by_seller(sales_data)

        for seller_id, total_amount in seller_totals.items():
            try:
                # Buscar dados do vendedor
                seller = self.get_seller_data(seller_id)

                # Calcular valor após comissão
                commission = total_amount * 0.10  # 10% de comissão
                payout_amount = total_amount - commission

                # Realizar pagamento
                payment = send_pix_payment(
                    token=self.token,
                    recipient_key=seller['pix_key'],
                    recipient_key_type=seller['pix_key_type'],
                    recipient_name=seller['name'],
                    recipient_document=seller['document'],
                    amount=payout_amount,
                    description=f'Repasse de vendas - {len(sales_data)} transações'
                )

                results['successful'].append({
                    'seller': seller['name'],
                    'gross_amount': total_amount,
                    'commission': commission,
                    'net_amount': payout_amount,
                    'transaction_id': payment['transactionId']
                })

                # Registrar repasse no banco de dados
                self.record_payout(seller_id, payment)

            except Exception as e:
                results['failed'].append({
                    'seller_id': seller_id,
                    'error': str(e)
                })

        return results

    def group_sales_by_seller(self, sales_data: list) -> dict:
        """Agrupa vendas por vendedor"""
        totals = {}
        for sale in sales_data:
            seller_id = sale['seller_id']
            totals[seller_id] = totals.get(seller_id, 0) + sale['amount']
        return totals

3. Sistema de Reembolso

class RefundSystem {
  constructor(token) {
    this.token = token;
  }

  async processRefund(orderId, refundReason) {
    // Buscar dados do pedido
    const order = await this.getOrderData(orderId);

    // Validar se reembolso é permitido
    if (!this.canRefund(order)) {
      throw new Error('Reembolso não permitido para este pedido');
    }

    // Realizar pagamento de volta ao cliente
    const refund = await sendPixPayment(
      this.token,
      order.customer.pixKey,
      order.customer.pixKeyType,
      order.customer.name,
      order.customer.document,
      order.amount,
      `Reembolso - Pedido ${orderId} - ${refundReason}`
    );

    // Atualizar status do pedido
    await this.updateOrderStatus(orderId, 'REFUNDED', refund.transactionId);

    // Enviar notificação ao cliente
    await this.notifyCustomer(order.customer.email, refund);

    return refund;
  }

  canRefund(order) {
    // Verificar se pedido foi pago e ainda está dentro do prazo
    const daysSincePurchase = (Date.now() - new Date(order.paidAt)) / (1000 * 60 * 60 * 24);
    return order.status === 'PAID' && daysSincePurchase <= 7;
  }
}

Formatos de Chave PIX

A tabela abaixo detalha o formato exato aceito para cada tipo de chave. O campo details.key é validado contra o details.keyType informado — se o formato não corresponder, a API retorna erro 400.

keyTypeFormato esperado em details.keyRegex de validaçãoExemplos válidosExemplos inválidos
DOCUMENTCPF: 11 dígitos numéricos^\d{11}$ (com validação de dígitos verificadores)12345678901123.456.789-01 (com pontuação), 1234567890 (10 dígitos)
DOCUMENTCNPJ: 14 dígitos numéricos^\d{14}$ (com validação de dígitos verificadores)1234567800019512.345.678/0001-95 (com pontuação)
EMAILEndereço de email válido^[^\s@]+@[^\s@]+\.[^\s@]{2,}$usuario@exemplo.comusuario@ (sem domínio)
PHONEDDD + número (10 ou 11 dígitos)^\d{10,11}$11999999999 (celular), 1133334444 (fixo)5511999999999 (com DDI), +5511999999999 (com +DDI)
RANDOMUUID (com ou sem hífens)^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$ ou ^[a-fA-F0-9]{32}$a1b2c3d4-e5f6-4890-abcd-ef1234567890a1b2c3d4 (incompleto)

Chave PHONE — não inclua o DDI (+55). Envie apenas DDD + número (10 ou 11 dígitos). A API adiciona o código de país +55 automaticamente antes de enviar ao banco liquidante.

Chave DOCUMENT — envie apenas números. CPFs e CNPJs não devem conter pontuação (pontos, barras ou hífens). Além da contagem de dígitos, a API valida os dígitos verificadores do CPF/CNPJ.

Auto-detecção de keyType

Se você deseja implementar detecção automática do keyType com base no valor da chave, use a seguinte lógica de prioridade:

  1. Contém @EMAIL
  2. Formato UUID v4 → RANDOM
  3. 14 dígitos + CNPJ válido (check digits) → DOCUMENT
  4. 11 dígitos + CPF válido (check digits) → DOCUMENT
  5. 10-11 dígitos + não é CPF válido → PHONE
function inferPixKeyType(key: string): 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM' | null {
  const trimmed = key.trim();

  // 1. UUID (chave aleatória / EVP)
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  if (uuidRegex.test(trimmed)) return 'RANDOM';

  // 2. Email
  if (trimmed.includes('@')) return 'EMAIL';

  // 3. Apenas dígitos → DOCUMENT ou PHONE
  const digits = trimmed.replace(/\D/g, '');

  // 14 dígitos → CNPJ
  if (digits.length === 14) return 'DOCUMENT';

  // 11 dígitos → CPF ou PHONE
  // A distinção é feita validando os dígitos verificadores do CPF
  if (digits.length === 11) {
    return isValidCpf(digits) ? 'DOCUMENT' : 'PHONE';
  }

  // 10 dígitos → telefone fixo (DDD + 8 dígitos)
  if (digits.length === 10) return 'PHONE';

  return null; // Formato não reconhecido
}

// Validação algorítmica de CPF (dígitos verificadores)
function isValidCpf(cpf: string): boolean {
  if (/^(\d)\1{10}$/.test(cpf)) return false; // Rejeita sequências como 11111111111
  let sum = 0;
  for (let i = 0; i < 9; i++) sum += parseInt(cpf[i]) * (10 - i);
  let check = 11 - (sum % 11);
  if (check >= 10) check = 0;
  if (check !== parseInt(cpf[9])) return false;
  sum = 0;
  for (let i = 0; i < 10; i++) sum += parseInt(cpf[i]) * (11 - i);
  check = 11 - (sum % 11);
  if (check >= 10) check = 0;
  return check === parseInt(cpf[10]);
}

Ambiguidade CPF vs. Telefone: Um valor de 11 dígitos pode ser tanto um CPF quanto um número de celular (DDD + 9 dígitos). A lógica acima usa a validação de dígitos verificadores do CPF para distinguir: se os check digits batem, é CPF; caso contrário, é telefone. Mesmo assim, recomendamos que o usuário final selecione explicitamente o tipo da chave na interface para evitar erros em casos limítrofes.

Validação de Chave PIX

Antes de enviar um pagamento, valide o formato da chave PIX localmente:

function validatePixKey(key: string, keyType: string): boolean {
  switch (keyType) {
    case 'DOCUMENT':
      // CPF: 11 dígitos ou CNPJ: 14 dígitos (apenas números)
      return /^\d{11}$/.test(key) || /^\d{14}$/.test(key);

    case 'EMAIL':
      // Email com domínio válido (TLD mínimo 2 caracteres)
      return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(key);

    case 'PHONE':
      // DDD + número: 10 dígitos (fixo) ou 11 dígitos (celular)
      // NÃO inclua DDI (+55) — a API adiciona automaticamente
      return /^\d{10,11}$/.test(key);

    case 'RANDOM':
      // UUID com hífens ou 32 caracteres hexadecimais sem hífens
      return /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/.test(key)
        || /^[a-fA-F0-9]{32}$/.test(key);

    default:
      return false;
  }
}

Verificação de Saldo

Sempre verifique o saldo antes de realizar pagamentos para evitar erros 400.

async function safePayment(
  token: string,
  amount: number,
  recipient: RecipientData
) {
  // Consultar saldo
  const balance = await getBalance(token);

  // Verificar se há saldo suficiente
  if (balance.netBalance < amount) {
    throw new Error(
      `Saldo insuficiente. Disponível: R$ ${balance.netBalance.toFixed(2)} | ` +
      `Necessário: R$ ${amount.toFixed(2)}`
    );
  }

  // Prosseguir com pagamento
  return await sendPixPayment(token, ...recipient, amount);
}

Monitoramento de Status

Para acompanhar a confirmação do pagamento:

async function monitorPaymentStatus(transactionId, timeout = 60000) {
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const status = await checkTransactionStatus(transactionId);

    if (status === 'CONFIRMED') {
      console.log('Pagamento confirmado!');
      return true;
    }

    if (status === 'ERROR') {
      throw new Error('Pagamento falhou');
    }

    // Aguardar 2 segundos antes de verificar novamente
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  throw new Error('Timeout: Pagamento não confirmado no tempo esperado');
}

Fluxo de Status da Transação

Após criar um PIX Cash-Out, a transação passa por um fluxo assíncrono:

POST /api/pix/cash-out


     ┌─────────┐
     │ PENDING  │  ← Resposta imediata da API (201)
     └────┬─────┘
          │  (processamento no banco liquidante)

    ┌─────┴──────┐
    ▼            ▼
┌──────────┐ ┌───────┐
│CONFIRMED │ │ ERROR │  ← Notificado via webhook
└──────────┘ └───────┘
  1. A API retorna status: "PENDING" imediatamente (201 Created)
  2. O pagamento é enviado ao banco liquidante para processamento
  3. O resultado final (CONFIRMED ou ERROR) é notificado via webhook de Cash-Out
  4. Alternativamente, use o endpoint de polling para consultar o status

A maioria dos pagamentos PIX é confirmada em poucos segundos. Recomendamos implementar o recebimento de webhooks como método principal de confirmação, e polling apenas como fallback.

Códigos de Resposta HTTP

CódigoDescriçãoSignificado
201Pagamento IniciadoTransferência PIX iniciada com sucesso (status PENDING)
400Dados InválidosCampos obrigatórios ausentes, formato inválido, saldo insuficiente ou limite excedido
401Não AutorizadoToken Bearer não fornecido, expirado ou inválido
409ConflitoexternalId já utilizado para esta conta
500Erro InternoErro inesperado no processamento

Erros de Negócio

Além dos códigos HTTP, a API retorna códigos de erro específicos no corpo da resposta. Alguns erros ocorrem de forma síncrona (na resposta da API) e outros de forma assíncrona (via webhook, quando o banco liquidante rejeita a operação).

Erros síncronos (resposta da API)

Retornados imediatamente na resposta HTTP com status 400:

Código do erroMensagemCausa
DUPLICATE_EXTERNAL_IDEsta transação já existe no sistemaO externalId já foi utilizado para esta conta
INSUFFICIENT_BALANCESaldo insuficiente para realizar esta operaçãoO saldo disponível é menor que o valor + taxa
TRANSACTION_LIMIT_EXCEEDEDUltrapassa o limite máximo por transaçãoO valor excede o limite configurado por transação
DAILY_LIMIT_EXCEEDEDVocê atingiu o limite diário de transaçõesO acumulado do dia excede o limite diário
NIGHT_LIMIT_EXCEEDEDO limite noturno é mais restritoOperação noturna excede o limite reduzido (20h–6h)
INVALID_AMOUNTValor inválidoValor menor que 0.01 ou com mais de 2 casas decimais
INVALID_PIX_KEYChave PIX inválidaO formato da chave não corresponde ao keyType informado

Erros assíncronos (via webhook)

Quando o banco liquidante rejeita a operação, a transação muda para status ERROR. Os campos errorCode e errorMessage são enviados no payload do webhook de Cash-Out:

{
  "event": "CashOut",
  "status": "ERROR",
  "transactionId": "12345",
  "externalId": "PAYMENT-987654-20240119",
  "errorCode": "TAX_ID_MISMATCH",
  "errorMessage": "O CPF/CNPJ informado não corresponde ao titular da conta.",
  ...
}
errorCodeerrorMessageCausaRetentável?
TAX_ID_MISMATCHO CPF/CNPJ informado não corresponde ao titular da conta.O details.document não confere com o dono da chave PIX na DICTNão
INVALID_TAX_IDCPF ou CNPJ inválido.O documento enviado falhou na validação do bancoNão
BLOCKED_ACCOUNTA conta de destino está bloqueada e não pode receber transferências.Conta de destino bloqueada judicialmente ou administrativamenteNão
ACCOUNT_CLOSEDA conta de destino está encerrada.A conta de destino foi encerradaNão
ORDER_REJECTEDA transação foi rejeitada pelo banco destinatário.O banco de destino recusou a operaçãoNão
PAYMENT_EXPIREDA transação expirou antes de ser processada.O tempo de processamento foi excedidoSim
SETTLEMENT_TIMEOUTO banco destinatário não respondeu a tempo.Timeout na comunicação com o banco de destinoSim

Para erros retentáveis (PAYMENT_EXPIRED, SETTLEMENT_TIMEOUT), é seguro reenviar a transação com um novo externalId.

Quando um erro assíncrono ocorre, a transação é automaticamente cancelada (rollback no ledger) e o saldo é estornado. Não é necessário tomar nenhuma ação adicional — apenas notifique o usuário final sobre o motivo da falha.

Consulte a Referência da API para detalhes completos dos campos de resposta.

Boas Práticas

Observações Importantes

  • Valor mínimo: R$ 0,01 | Casas decimais: Máximo 2
  • externalId: Máximo 50 caracteres, único por conta (idempotência)
  • description: Máximo 140 caracteres (opcional)
  • details.name: Máximo 100 caracteres — informativo, não validado pelo banco
  • details.document: Obrigatório para todos os tipos de chave — validado pelo banco liquidante
  • Chave PHONE: Envie apenas DDD + número (10-11 dígitos). A API adiciona +55 automaticamente

Próximos Passos

Nesta página