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/jsonRequest 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
valuenumberobrigatorioValor do pagamento em reais (BRL). Deve ter no máximo 2 casas decimais.
Mínimo: 0.01
Exemplo: 250.50
detailsobjectobrigatorioInformações da chave PIX de destino.
details.keystringobrigatorioChave 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.keyTypestringobrigatorioTipo 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 emailPHONE— 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.namestringobrigatorioNome 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.documentstringobrigatorioCPF 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: odetails.documentdeve ser idêntico aodetails.key(ambos são o CPF/CNPJ). Se divergirem, o banco rejeitará comTAX_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"
externalIdstringobrigatorioIdentificador ú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"
descriptionstringDescriçã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 presenteID interno da transação gerada pela Avista.
Exemplo: "9876"
externalIdstringsempre presenteID externo fornecido na requisição (mesmo valor do input).
Exemplo: "PAYMENT-987654-20240119"
statusstringsempre presenteStatus atual da transação.
Valores possíveis:
PENDING: Pagamento em processamentoCONFIRMED: Pagamento confirmado e finalizadoERROR: Erro no processamento
Exemplo: "PENDING"
Nota: A maioria dos pagamentos PIX é confirmada em poucos segundos
generateTimestringsempre presenteData 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 totals3. 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.
keyType | Formato esperado em details.key | Regex de validação | Exemplos válidos | Exemplos inválidos |
|---|---|---|---|---|
DOCUMENT | CPF: 11 dígitos numéricos | ^\d{11}$ (com validação de dígitos verificadores) | 12345678901 | 123.456.789-01 (com pontuação), 1234567890 (10 dígitos) |
DOCUMENT | CNPJ: 14 dígitos numéricos | ^\d{14}$ (com validação de dígitos verificadores) | 12345678000195 | 12.345.678/0001-95 (com pontuação) |
EMAIL | Endereço de email válido | ^[^\s@]+@[^\s@]+\.[^\s@]{2,}$ | usuario@exemplo.com | usuario@ (sem domínio) |
PHONE | DDD + número (10 ou 11 dígitos) | ^\d{10,11}$ | 11999999999 (celular), 1133334444 (fixo) | 5511999999999 (com DDI), +5511999999999 (com +DDI) |
RANDOM | UUID (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-ef1234567890 | a1b2c3d4 (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:
- Contém
@→EMAIL - Formato UUID v4 →
RANDOM - 14 dígitos + CNPJ válido (check digits) →
DOCUMENT - 11 dígitos + CPF válido (check digits) →
DOCUMENT - 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
└──────────┘ └───────┘- A API retorna
status: "PENDING"imediatamente (201 Created) - O pagamento é enviado ao banco liquidante para processamento
- O resultado final (
CONFIRMEDouERROR) é notificado via webhook de Cash-Out - 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ódigo | Descrição | Significado |
|---|---|---|
201 | Pagamento Iniciado | Transferência PIX iniciada com sucesso (status PENDING) |
400 | Dados Inválidos | Campos obrigatórios ausentes, formato inválido, saldo insuficiente ou limite excedido |
401 | Não Autorizado | Token Bearer não fornecido, expirado ou inválido |
409 | Conflito | externalId já utilizado para esta conta |
500 | Erro Interno | Erro 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 erro | Mensagem | Causa |
|---|---|---|
DUPLICATE_EXTERNAL_ID | Esta transação já existe no sistema | O externalId já foi utilizado para esta conta |
INSUFFICIENT_BALANCE | Saldo insuficiente para realizar esta operação | O saldo disponível é menor que o valor + taxa |
TRANSACTION_LIMIT_EXCEEDED | Ultrapassa o limite máximo por transação | O valor excede o limite configurado por transação |
DAILY_LIMIT_EXCEEDED | Você atingiu o limite diário de transações | O acumulado do dia excede o limite diário |
NIGHT_LIMIT_EXCEEDED | O limite noturno é mais restrito | Operação noturna excede o limite reduzido (20h–6h) |
INVALID_AMOUNT | Valor inválido | Valor menor que 0.01 ou com mais de 2 casas decimais |
INVALID_PIX_KEY | Chave PIX inválida | O 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.",
...
}errorCode | errorMessage | Causa | Retentável? |
|---|---|---|---|
TAX_ID_MISMATCH | O CPF/CNPJ informado não corresponde ao titular da conta. | O details.document não confere com o dono da chave PIX na DICT | Não |
INVALID_TAX_ID | CPF ou CNPJ inválido. | O documento enviado falhou na validação do banco | Não |
BLOCKED_ACCOUNT | A conta de destino está bloqueada e não pode receber transferências. | Conta de destino bloqueada judicialmente ou administrativamente | Não |
ACCOUNT_CLOSED | A conta de destino está encerrada. | A conta de destino foi encerrada | Não |
ORDER_REJECTED | A transação foi rejeitada pelo banco destinatário. | O banco de destino recusou a operação | Não |
PAYMENT_EXPIRED | A transação expirou antes de ser processada. | O tempo de processamento foi excedido | Sim |
SETTLEMENT_TIMEOUT | O banco destinatário não respondeu a tempo. | Timeout na comunicação com o banco de destino | Sim |
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 bancodetails.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
+55automaticamente