Pular para o conteúdo
LIQFYdocs
PTENENEN
Ir para o painel

Saques

O termo de produto é Saque (payout). O endpoint atual é /v1/withdrawals — o nome de fio em uso hoje, ainda não renomeado para /v1/payouts.

Tira saldo para fora da sua carteira Liqfy, rumo a um destino real — uma chave Pix ou uma conta bancária (TED). É o inverso do pagamento Pix: pagamento credita sua carteira, saque debita.

Consultar pelo número do pedido: envie esse número como Idempotency-Key ao criar o saque. Depois consulte GET /v1/withdrawals/by-idempotency-key/{numero-do-pedido} com uma chave de API da mesma conta. Um saque criado sem essa chave não pode ser encontrado pelo número do pedido. Veja os exemplos de consulta.

Comprovante: GET /v1/withdrawals/{id}/receipt?format=json devolve dados estruturados; sem format, continua devolvendo o PDF no envelope {contentType, base64}. Veja Comprovante e Contrato do comprovante JSON.

No callback payout.paid, o E2E Pix pode vir em data.object.settlement.end_to_end_id quando já foi confirmado. Em saques divididos, consulte os E2Es de settlement.legs[]; a ausência de E2E não indica falha do saque. Veja Webhooks.

A disponibilidade dos trilhos é por conta. TED depende de o módulo correspondente estar habilitado para você. Confirme com seu contato na Liqfy antes de construir em cima de um deles.

#O fluxo, de relance

text
1. POST /v1/withdrawals/validate-pix-key   (opcional — checagem prévia)
2. POST /v1/withdrawals                    (cria a solicitação e reserva o valor na hora)
3. A Liqfy despacha — automaticamente ou após aprovação
4. O webhook payout.paid (ou payout.failed) chega no seu endpoint

Saque usa a mesma chave de API das cobranças — Authorization: Bearer <chave>, chamada do seu servidor, nunca do navegador ou do app do cliente final. (O cabeçalho apikey ainda é aceito.) Chamadas por chave de API também passam pela lista de IPs permitidos da sua conta. A carteira é debitada na criação; se o saque também precisa de aprovação depende da configuração da sua conta.

Mande sempre o cabeçalho Idempotency-Key na criação — ele é o canal principal de idempotência (veja Criar o saque).

#1. Validar uma chave Pix (recomendado)

Antes de criar o saque, confira se a chave de destino está bem formada.

POST /v1/withdrawals/validate-pix-key

json
{
  "key": "12345678909",
  "type": "CPF"
}

Resposta 201 Created

json
{
  "isValid": true,
  "type": "CPF",
  "normalized": "12345678909"
}

Valores aceitos em type

TipoFormato
CPF11 dígitos (pontos e traços são removidos), dígito verificador mod-11. Números com todos os dígitos iguais são recusados.
CNPJ14 dígitos (pontos, traços e barras são removidos), dígito verificador mod-11. Números com todos os dígitos iguais são recusados.
EMAILFormato local@dominio.tld, convertido para minúsculas, até 77 caracteres.
PHONE+55 seguido de 10 ou 11 dígitos (+5511999999999). Espaços, traços e parênteses são removidos.
RANDOMChave aleatória — UUID v4, em minúsculas.
(omitido)Detectado automaticamente pelo valor da chave.

O tipo não diferencia maiúsculas de minúsculas. Um tipo conhecido declarado é validado à risca — declarar CPF para um e-mail reprova. Declarar qualquer outro tipo (por exemplo EVP) é ignorado: a chave é detectada automaticamente e a resposta informa o tipo detectado.

Ordem da detecção automática: UUID v4 → RANDOM; começa com +55 → PHONE; formato de e-mail → EMAIL; 11 dígitos → CPF; 14 dígitos → CNPJ. Um telefone enviado sem +55 (11 dígitos) é, portanto, detectado como CPF — mande telefones em E.164 ou declare PHONE. Quando nada casa, a resposta é isValid: false com type: "CPF" como marcador e error: "Unable to detect PIX key type".

O endpoint responde 201 mesmo quando a chave é inválida — a falha mora no corpo, nunca no status HTTP. Só key ausente ou vazia dá 400.

Falha 201 Created

json
{
  "isValid": false,
  "type": "CPF",
  "normalized": "12345678900",
  "error": "CPF checksum invalid"
}

Valores possíveis de error

errorTipo
CPF must be 11 digits / CPF checksum invalidCPF
CNPJ must be 14 digits / CNPJ checksum invalidCNPJ
Invalid email format / Email exceeds 77 charactersEMAIL
Phone must start with +55 / Phone must have 10 or 11 digits after +55PHONE
Random key must be a valid UUID v4RANDOM
Unable to detect PIX key typedetecção automática falhou
PIX key must not be emptychave só com espaços

#2. Cotação, taxa e saldo sacável

Três ajudas de leitura, com a mesma chave de API da criação. currency é opcional em todas (padrão BRL).

#Cotação — quanto enviar para o recebedor receber um valor exato

POST /v1/withdrawals/quote — corpo { value, currency? }, value inteiro maior que zero. Precificado como Pix. Corpo inválido → 400.

Resposta 201 Created

json
{
  "value": 50000,
  "fee": 100,
  "total": 50100
}

total é o bruto que você paga para o recebedor receber exatamente value — a taxa é gross-up, o destino nunca a absorve.

bash
curl -X POST https://api.liqfy.com.br/v1/withdrawals/quote \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "value": 50000 }'
ts
import { LiqfyClient } from '@liqfy/node';

const client = new LiqfyClient({ apiKey: process.env.LIQFY_API_KEY! });

const quote = await client.payouts.quote({ value: 50000 });

Python client.payouts.quote(value=50000), PHP $client->payouts->quote(50000), .NET client.Payouts.QuoteAsync(50000).

#Taxa — o que a criação cobraria

GET /v1/withdrawals/payout-fee

QueryObrigatórioDescrição
amountsimInteiro, 1 a 10.000.000.000 (menor unidade).
currencynãoPadrão BRL. De 3 a 8 caracteres.
typenãoPIX (padrão) ou TED.

Qualquer valor fora desses limites → 400.

Resposta 200 OK

json
{
  "amount": 50000,
  "currency": "BRL",
  "fee": 100,
  "total": 50100
}

fee é o que o POST /v1/withdrawals cobraria por esse valor (já incluindo o complemento de pernas acima do teto do Pix); total = amount + fee.

bash
curl "https://api.liqfy.com.br/v1/withdrawals/payout-fee?amount=50000&currency=BRL&type=PIX" \
  -H "Authorization: Bearer $LIQFY_API_KEY"
ts
const fee = await client.payouts.payoutFee({ amount: 50000 });

Python client.payouts.payout_fee(amount=50000), PHP $client->payouts->payoutFee(50000), .NET client.Payouts.PayoutFeeAsync(50000).

#Saldo sacável e parâmetros de taxa

GET /v1/withdrawals/payout-info — sem efeito colateral, feito para o botão "sacar tudo".

Resposta 200 OK

json
{
  "currency": "BRL",
  "available": 1000000,
  "pendingAdjustment": 0,
  "fee": { "fixed": 100, "variablePct": 0, "minimum": 100 },
  "maxWithdrawable": 999900
}

available é o saldo gastável. maxWithdrawable é o maior valor bruto pedível com a taxa já descontada. fee é o conjunto de parâmetros aplicado hoje (fixed, variablePct, minimum).

bash
curl "https://api.liqfy.com.br/v1/withdrawals/payout-info?currency=BRL" \
  -H "Authorization: Bearer $LIQFY_API_KEY"
ts
const info = await client.payouts.payoutInfo();

Python client.payouts.payout_info(), PHP $client->payouts->payoutInfo(), .NET client.Payouts.PayoutInfoAsync().

#3. Criar o saque

POST /v1/withdrawals

Idempotência. Mande o cabeçalho Idempotency-Key — o canal principal, com espaços aparados e no máximo 255 caracteres (cabeçalho maior → 400 "Idempotency-Key must be at most 255 characters"). O campo idempotencyKey no corpo (de 1 a 255 caracteres) é um fallback para quem não usa SDK; quando os dois existem, vale o cabeçalho. A mesma chave devolve o mesmo saque: 201 de novo, sem segundo débito. Se a reserva falhar, a linha vira REJECTED e a chave é liberada — depois de depositar você pode repetir com a mesma chave.

Sem chave, a única proteção é um quase-duplicado: o mesmo type, amount e pixKey em 90 segundos responde 409 sem error.code estável. Mande uma chave de idempotência e você nunca depende disso.

CampoTipoObrigatórioDescrição
typeenumsimPIX ou TED. (CRYPTO existe, mas é só pelo painel — veja abaixo.)
amountinteirosimInteiro positivo, menor unidade da moeda. No modo padrão gross é o que o recebedor recebe; a taxa é debitada por cima.
currencystringnãoPadrão BRL.
pixKeystringquando PIXChave Pix de destino.
pixKeyTypeenumnãoCPF, CNPJ, EMAIL, PHONE, RANDOM. Omitido ou desconhecido (por exemplo EVP): ignorado, a chave é detectada automaticamente.
bankCodestringquando TEDCódigo do banco (FEBRABAN).
bankAgencystringquando TEDAgência.
bankAccountstringquando TEDNúmero da conta.
walletIdstringnãoCarteira OPERATIONAL explícita e gastável em currency. Omitido → seleção automática (a carteira padrão da moeda se tiver saldo, senão qualquer carteira gastável com saldo suficiente). Desconhecida → WALLET_NOT_FOUND; não gastável → WALLET_NOT_SPENDABLE; nada cobre o débito → INSUFFICIENT_BALANCE.
amountTypeenumnãogross (padrão) ou net. net é legado e ainda aceito: o débito é amount e o recebedor recebe amount − taxa (fee >= amount → 400).
idempotencyKeystringnãoFallback — prefira o cabeçalho Idempotency-Key.
totpCodestringnãoSó no painel: o código 2FA quando a conta exige 2FA em saques. Ignorado em chamadas por chave de API.

Chaves desconhecidas (description, cryptoAddress, cryptoNetwork, cryptoCurrency, qualquer outra) são removidas em silêncio — a API nunca guarda nem devolve esses campos. cryptoAddress, cryptoNetwork e cryptoCurrency ainda podem aparecer como campos de resposta legados, somente leitura; não são entrada de criação.

Saque cripto é só pelo painel. type: "CRYPTO" (com cryptoWalletAddressId e cryptoEstimateId) só é aceito de um usuário logado no painel; com chave de API responde 403 com o token CRYPTO_PANEL_ONLY. O mesmo vale para POST /v1/withdrawals/crypto/estimate; GET /v1/withdrawals/crypto/availability responde { "enabled": false } para chave de API. No painel, o saque cripto precisa ser em BRL, a partir de R$ 5.000,00 (500000), sem net e com os dois ids (senão 400 INVALID_CRYPTO_WITHDRAWAL), e sempre pede o código 2FA.

Exemplo — saque Pix

bash
curl -X POST https://api.liqfy.com.br/v1/withdrawals \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: saque-2026-S17" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PIX",
    "amount": 50000,
    "pixKey": "12345678909",
    "pixKeyType": "CPF"
  }'

Exemplo — saque TED (os dados bancários são três strings planas)

bash
curl -X POST https://api.liqfy.com.br/v1/withdrawals \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: saque-2026-S17-ted" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "TED",
    "amount": 50000,
    "bankCode": "001",
    "bankAgency": "1234",
    "bankAccount": "56789012"
  }'
ts
const payout = await client.payouts.create(
  { type: 'PIX', amount: 50000, pixKey: '12345678909', pixKeyType: 'CPF' },
  { idempotencyKey: 'saque-2026-S17' },
);
// TED: { type: 'TED', amount: 50000, bankCode: '001', bankAgency: '1234', bankAccount: '56789012' }

Python client.payouts.create(input, idempotency_key=…), PHP $client->payouts->create($input, $idempotencyKey), .NET client.Payouts.CreateAsync(input, idempotencyKey).

Resposta 201 Created

json
{
  "id": "f6a7b8c9-d0e1-4234-9f01-234567890123",
  "type": "PIX",
  "status": "PENDING",
  "amount": 50000,
  "fee": 100,
  "netAmount": 50000,
  "currency": "BRL",
  "source": "API",
  "walletId": "2c9f8e7d-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
  "pixKey": "12345678909",
  "pixKeyType": "CPF",
  "idempotencyKey": "saque-2026-S17",
  "createdAt": "2026-04-25T16:10:00.000Z",
  "updatedAt": "2026-04-25T16:10:00.000Z"
}

A carteira é debitada na criação. No modo gross (o padrão) o débito é amount + fee (aqui 50.100) e o recebedor recebe exatamente amount — por isso netAmount é igual a amount. O valor fica reservado a partir daqui; a aprovação só decide quando o saque é despachado.

Um 201 já pode voltar APPROVED ou PROCESSING: o auto-payout aprova e despacha na hora quando a política da sua conta permite. "Sempre começa PENDING" não é garantido.

Quando o saque não vai. O contrato completo da recusa — o error.code da recusa imediata por saldo, os códigos de falha (failure_code) e quais deles estornam sozinhos — está em Falhas de saque.

#Criar pelo painel

  • No painel, só o dono ou um admin da conta cria saque (membro comum → 403, code = WITHDRAWAL_CANCEL_ROLE_REQUIRED).
  • Quando a conta exige 2FA em saques (e sempre, no cripto), o pedido precisa de um totpCode atual. Usuário sem 2FA ativado recebe 403 ("Esta conta exige 2FA para saques…"); código ausente ou inválido recebe 401 ("Código 2FA inválido ou ausente.").
  • Saques criados no painel têm source: "PANEL"; por chave de API, source: "API".

#Pedido recusado deixa rastro

Toda recusa 4xx do POST /v1/withdrawals (saldo, KYC, validação, limites, regras…) é registrada no log de auditoria da conta como WITHDRAWAL_REJECTED e aparece na tela de saques, com valor, origem e motivo — por exemplo "Pedido de saque de R$ 500,00 recusado: saldo insuficiente (disponível R$ 120,00), via API". O mesmo pedido repetido com a mesma chave de idempotência e o mesmo código de recusa é registrado uma vez por hora. Recusas pela lista de IPs acontecem antes desta etapa e não entram aqui.

#4. Ciclo de vida

text
PENDING ─▶ APPROVED ─▶ PROCESSING ─▶ CONFIRMING ─▶ COMPLETED            ✓ dinheiro entregue
                                                    └▶ PARTIALLY_COMPLETED   entregue em partes

Desfechos terminais incluem também FAILED, REJECTED, CANCELLED e REVERSED. Dez status no total; os seis terminais (COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED, CANCELLED, REVERSED) não mudam mais.

StatusEfeito na carteira
PENDINGDebitado na criação (valor + taxa reservados)
APPROVEDReservado — aprovado, aguardando despacho
PROCESSINGReservado — enviado para liquidação
CONFIRMINGReservado — aguardando a confirmação da instituição de destino
COMPLETEDDebitado (terminal) — entregue
PARTIALLY_COMPLETEDDebitado (terminal) — parte das pernas entregue, parte falhou (veja legs); as pernas que falharam são estornadas
FAILEDEstornado automaticamente — o despacho falhou (failure_code). O estorno pode ficar retido até a confirmação (veja Falhas de saque)
REJECTEDEstornado — recusado na revisão, ou pela reserva (system:reserve-failed)
CANCELLEDEstornado (valor + taxa) — cancelado por você antes do despacho, ou quando um admin desativa a conta (motivo "Cancelado pelo admin; valor devolvido ao saldo")
REVERSEDEntregue e depois devolvido pelo recebedor (reversalReason, reversalAmount, reversalStatus). O valor devolvido é creditado automaticamente somente quando a marca habilita essa opção — veja Saques devolvidos

REJECTED não é só "negado pelo admin": uma criação cuja reserva falha também vira REJECTED, com o valor devolvido.

Despacho incerto fica em PROCESSING. Quando a chamada ao provedor estoura o tempo, o Pix pode ou não ter saído. O saque (ou a perna) fica em PROCESSING enquanto a Liqfy confirma com o provedor — só falha e estorna com prova positiva de que o Pix não saiu. Quando a conferência prova isso, a mensagem de falha é "Saque não efetivado pelo provedor. O valor foi devolvido ao saldo."

#Campos de resposta além do corpo de criação

O GET /v1/withdrawals/{id}, a lista e a resposta do cancelamento acrescentam:

CampoO que é
legs[]{ id, legIndex, amount, status, endToEndId, failureCode, refunded, reversalCredited, reversalEndToEndId, reversedAmount, reversedAt, createdAt, updatedAt }. Um saque Pix acima do teto por transação é dividido em N pernas na aprovação; legIndex começa em 1. Pernas substituídas por um reenvio interno não aparecem.
legSummaryAgregado das legs: { count, paidOut, returned, awaitingReturn, pendingDecision, inFlight, total }.
recipientNameNome do titular informado pelo provedor na liquidação (prefere uma perna concluída), quando disponível.
reversalReason, reversalAmount, reversalStatusPresentes quando o saque foi revertido.

Campos da perna

  • status — PENDING, PROCESSING, COMPLETED, FAILED ou REVERSED.
  • endToEndId — o E2E Pix da própria perna, para conciliar com o extrato do banco.
  • failureCode — de um conjunto fechado, nunca texto do provedor: INVALID_PIX_KEY, ACCOUNT_CLOSED, ACCOUNT_BLOCKED, KYC_REJECTED, LIMIT_REJECTED, DESTINATION_UNAVAILABLE, DESTINATION_NOT_ALLOWED, INSUFFICIENT_BALANCE, TIMEOUT, PROVIDER_CANCELLED, PROVIDER_REJECTED, UNKNOWN. null quando a perna não falhou.
  • refunded — só em perna FAILED: true quando o crédito do estorno já existe no razão (estorno da perna ou do saque inteiro). false significa estorno ainda retido.
  • reversalCredited — só em perna REVERSED: true quando o valor devolvido já foi creditado no seu saldo.
  • reversalEndToEndId, reversedAmount, reversedAt — dados da devolução, em perna REVERSED.

legSummary

CampoSoma de
paidOutPernas COMPLETED — o que chegou ao destino.
returnedPernas FAILED já estornadas + pernas REVERSED já creditadas. Só o valor das pernas: quando o saque inteiro é estornado, a taxa devolvida não entra aqui.
awaitingReturnPernas FAILED cujo estorno ainda não caiu.
pendingDecisionPernas REVERSED cujo valor devolvido ainda não foi creditado a você.
inFlightPernas PENDING / PROCESSING.
totalTodas as pernas — igual ao netAmount do saque.

#Saques devolvidos

Quando o recebedor devolve um Pix pago, a perna vira REVERSED assim que a devolução é confirmada no extrato do provedor (só é aceita a devolução do valor integral da perna). O saque vira REVERSED quando todas as pernas ativas foram devolvidas; com parte das pernas devolvida, o saque mantém o status e as pernas devolvidas mostram REVERSED.

O dinheiro devolvido volta para a Liqfy, não direto para a sua carteira. Ele é creditado no seu saldo sem a taxa automaticamente só quando a sua marca habilita isso e a devolução não tem indício de fraude ou suspeita (códigos BE08, FR01); fora disso, uma pessoa decide creditar ou reter. Acompanhe legs[].reversalCredited e legSummary.pendingDecision.

reversalReason usa estes rótulos para os códigos de devolução do BACEN:

CódigoRótulo
BE08Devolução por suspeita de fraude
FR01Conta do recebedor sob suspeita de fraude
MD06Devolução solicitada pelo pagador
AC03Chave/conta do recebedor inválida
AC04Conta do recebedor encerrada
AC06Conta do recebedor bloqueada
AM09Valor incorreto
SL02Devolução por solicitação do participante
DUPLPagamento em duplicidade
outroDevolução PIX (<código>), ou Devolução PIX sem código

#Cancelar um saque pendente

POST /v1/withdrawals/{id}/cancel — corpo opcional { "reason": "..." } (aparado, até 500 caracteres; padrão "Cancelado pelo lojista."). Mesma autenticação da criação. Aceita o id puro ou o id po_.

  • Só enquanto o saque está PENDING (nada foi despachado). Ele vira CANCELLED, valor + taxa voltam para a carteira na mesma transação, e o desfecho dispara os webhooks abaixo. Resposta 200 com o saque (formato do retrieve).
  • Já aprovado, despachado ou encerrado → 409, code = WITHDRAWAL_NOT_CANCELLABLE. Linhas que só o suporte cancela (source MANUAL, acima do piso de aprovação manual, fora de BRL) também respondem 409: cancele pelo suporte.
  • No painel, só o dono ou um admin da conta cancela (membro comum → 403, code = WITHDRAWAL_CANCEL_ROLE_REQUIRED). Pela API, vale a chave da conta. A conta recebe um e-mail "Saque cancelado".
  • Saque de outra conta → 404 — e o 404 nunca confirma que o id existe.
bash
curl -X POST https://api.liqfy.com.br/v1/withdrawals/f6a7b8c9-d0e1-4234-9f01-234567890123/cancel \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Pedido duplicado" }'
ts
await client.payouts.cancel('f6a7b8c9-d0e1-4234-9f01-234567890123', { reason: 'Pedido duplicado' });

Python client.payouts.cancel(id, reason=…), PHP $client->payouts->cancel($id, $reason), .NET client.Payouts.CancelAsync(id, reason).

#Comprovante

GET /v1/withdrawals/{id}/receipt — não é só para COMPLETED: o documento existe para todo status terminal (COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED, CANCELLED, REVERSED). Não terminal → 400. format opcional: pdf (padrão) ou json; qualquer outro valor → 400.

Resposta 200 OK

json
{
  "contentType": "application/pdf",
  "base64": "JVBERi0xLjQKJcTl8uXrp/Og0MTGCg=="
}

Só o COMPLETED tem o título "Comprovante de Transferência PIX"; os demais documentos registram o desfecho e dizem que não são comprovante de pagamento:

StatusTítuloRótulo do valor
COMPLETEDComprovante de Transferência PIXValor transferido
PARTIALLY_COMPLETEDTransferência Parcialmente ConcluídaValor solicitado
FAILEDTransferência Não RealizadaValor solicitado
REJECTEDTransferência Não AprovadaValor solicitado
CANCELLEDTransferência CanceladaValor solicitado
REVERSEDTransferência DevolvidaValor da transferência

O documento só diz que o valor voltou para o seu saldo quando esse crédito existe no razão — um saque cancelado sem estorno nunca afirma devolução. O E2E do BACEN só aparece em documento pago; o id da transação no provedor nunca aparece. CPF/CNPJ são mascarados; a chave Pix de destino só aparece quando faltam nome e documento do recebedor. A linha de motivo só aparece para motivos reconhecidos como seguros para o cliente (chave inválida, dados do titular que não conferem, limite, conta de destino não apta, cancelamento, indisponibilidade genérica); mensagem bruta do provedor nunca chega ao documento.

bash
curl https://api.liqfy.com.br/v1/withdrawals/f6a7b8c9-d0e1-4234-9f01-234567890123/receipt \
  -H "Authorization: Bearer $LIQFY_API_KEY"
ts
const receipt = await client.payouts.receipt('f6a7b8c9-d0e1-4234-9f01-234567890123');

Python client.payouts.receipt(id), PHP $client->payouts->receipt($id), .NET client.Payouts.ReceiptAsync(id).

#5. Webhooks

Assine payout.created, payout.approved, payout.paid e payout.failed. Todos usam o envelope versionado evt_ e a assinatura com timestamp. A aprovação indica processing, antes da liquidação. Veja Webhooks.

json
{
  "id": "evt_5f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
  "object": "event",
  "api_version": "2026-07-23",
  "type": "payout.paid",
  "created_at": "2026-04-25T16:11:42.000Z",
  "data": {
    "object": {
      "id": "po_f6a7b8c9-d0e1-4234-9f01-234567890123",
      "object": "payout",
      "status": "paid"
    }
  }
}

data.object.status usa o vocabulário público: pending, processing, paid, failed. FAILED, REJECTED e CANCELLED chegam todos como payout.failed com status: "failed".

payout.failed contém data.object.failure_code e data.object.failure_message quando disponíveis. Use o código estável para decidir como agir. destination_not_allowed significa que o provedor recusou esse tipo de destino; tente novamente só com outro tipo de chave Pix ou destino. Veja Falhas de saque.

#6. Listar seus saques

GET /v1/withdrawals — mesma chave de API do endpoint de criação.

QueryPadrãoDescrição
page1Número da página (inteiro; outra coisa → 400).
limit20Inteiro. Teto rígido em 100 — valor maior é reduzido em silêncio, sem erro.
status—CSV de qualquer um dos vocabulários (abaixo). Token desconhecido → 400 ("Invalid withdrawal status: …").
source—API, PANEL, MANUAL ou INDEFINIDO (linhas sem source). Qualquer outro valor é ignorado em silêncio e a lista não é filtrada.
orderdescPor data de criação. asc para o mais antigo primeiro; qualquer outra coisa é desc.
idempotency_key—Chave exata, de 1 a 255 caracteres após aparar (senão 400). Zero ou um resultado.

Os dois vocabulários valem em status, e os valores de fio na resposta são os internos em maiúsculas. Uma palavra pública em minúsculas vira o seu conjunto; qualquer outro token é comparado ao status interno sem diferenciar maiúsculas (PROCESSING = só PROCESSING, enquanto processing = APPROVED, PROCESSING e CONFIRMING; reversed = REVERSED).

Filtro públicoValores de fio internos
pendingPENDING
processingAPPROVED, PROCESSING, CONFIRMING
paidCOMPLETED, PARTIALLY_COMPLETED
failedFAILED, REJECTED, CANCELLED
(nenhum)REVERSED
bash
curl "https://api.liqfy.com.br/v1/withdrawals?page=1&limit=20&status=paid&source=API&order=desc" \
  -H "Authorization: Bearer $LIQFY_API_KEY"
ts
const page = await client.payouts.list({ limit: 20, status: 'paid', page: 1, source: 'API', order: 'desc' });

Python client.payouts.list(limit=…, status=…, page=…, source=…, order=…), PHP $client->payouts->list([...]), .NET client.Payouts.ListAsync(...).

Resposta 200 OK

json
{
  "data": [
    {
      "id": "f6a7b8c9-d0e1-4234-9f01-234567890123",
      "type": "PIX",
      "status": "COMPLETED",
      "amount": 50000,
      "fee": 100,
      "netAmount": 50000,
      "currency": "BRL",
      "source": "API",
      "completedAt": "2026-04-25T16:11:42.000Z",
      "recipientName": "Maria Silva",
      "legs": [
        {
          "id": "c1d2e3f4-0000-0000-0000-000000000001",
          "legIndex": 1,
          "amount": 50000,
          "status": "COMPLETED",
          "endToEndId": "E12345678202604251611abcdef123456",
          "failureCode": null,
          "refunded": false,
          "reversalCredited": false
        }
      ],
      "legSummary": { "count": 1, "paidOut": 50000, "returned": 0, "awaitingReturn": 0, "pendingDecision": 0, "inFlight": 0, "total": 50000 },
      "...": "..."
    }
  ],
  "total": 17,
  "page": 1,
  "limit": 20
}

#7. Limites e regras

  • Teto por transação no Pix — R$ 15.000,00 por padrão (configuração da plataforma). Acima do teto o saque é dividido em N legs na aprovação: cada perna é preenchida até o teto e o resto vai na última (R$ 110.000,00 → 7 × R$ 15.000,00 + 1 × R$ 5.000,00). Cada perna é um Pix próprio, com E2E próprio, e pode falhar de forma independente.
  • Limites de saque por conta — minWithdrawalAmount / maxWithdrawalAmount por solicitação, dailyPayoutMaxAmount / dailyPayoutMaxCount por dia (null = sem limite). Fora dos limites: AMOUNT_BELOW_MINIMUM, AMOUNT_ABOVE_MAXIMUM ou DAILY_PAYOUT_LIMIT_EXCEEDED.
  • A carteira precisa cobrir valor + taxa — débito parcial nunca acontece; a solicitação é recusada de uma vez.
  • Aprovação — saques a partir de manualApprovalThresholdCents (padrão R$ 15.001,00 — um centavo acima do teto do Pix, então qualquer Pix de perna única até o teto pode aprovar automaticamente), todo saque fora de BRL e linhas enviadas para revisão manual precisam de aprovação. O auto-payout é política de conta (autoPayoutEnabled, autoWithdrawPct — trava por percentual do saldo, padrão 50, tipos de saque elegíveis, teto do saque automático e se todos os saques exigem aprovação manual) — peça ao suporte para revisar a sua. Um saque elegível é despachado em segundos na criação; uma varredura a cada 5 minutos reavalia, pela mesma política, os saques que continuam PENDING.
  • Freio de velocidade — uma rajada fora do normal de saques da conta, ou várias contas pagando a mesma chave Pix em pouco tempo, pode mandar o saque para aprovação manual em vez do auto-payout. Ele nunca recusa o saque.
  • Regras de saque — regras da plataforma podem recusar um saque (a recusa traz ruleId e reasonKey: withdrawal.rule.pixKeyTypeNotAllowed ou withdrawal.rule.notAllowed) ou exigir aprovação para ele.
  • TED — restrito a bancos brasileiros (lista de códigos FEBRABAN). Liquidação no mesmo dia útil se aprovado até 16:30 (horário de Brasília).

#Códigos de erro

Ramifique em error.code. A message é para log; o código é o contrato. Exceção: alguns tokens (WALLET_NOT_FOUND, WALLET_NOT_SPENDABLE, TENANT_KYC_REQUIRED, CRYPTO_PANEL_ONLY) são texto puro de mensagem — as linhas deles mostram o error.code de fallback, e você casa o token em error.message. Mapa completo: Erros.

HTTPerror.codeQuando
400INSUFFICIENT_BALANCENenhuma carteira cobre o débito (details: available, requested, currency). 400 neste caminho — não é 422.
400invalid_requestO walletId explícito é desconhecido, inativo, de outra moeda ou não gastável. Token em error.message: WALLET_NOT_FOUND / WALLET_NOT_SPENDABLE.
400invalid_requestCorpo inválido, Idempotency-Key com mais de 255 caracteres, filtro status inválido ou chave de idempotência inválida na consulta.
400AMOUNT_BELOW_MINIMUM / AMOUNT_ABOVE_MAXIMUMFora dos limites de saque da conta.
422DAILY_PAYOUT_LIMIT_EXCEEDEDO teto diário de valor ou de quantidade da conta foi atingido.
403account_suspendedConta suspensa ou bloqueada.
403forbiddenO KYC da conta não está ativo (quando o gate de KYC está em modo de exigência). Token em error.message: TENANT_KYC_REQUIRED.
403forbiddentype: "CRYPTO" (ou a estimativa cripto) chamado com chave de API. Token em error.message: CRYPTO_PANEL_ONLY.
403WITHDRAWAL_CANCEL_ROLE_REQUIREDO usuário não é dono nem administrador da conta: criação ou cancelamento de saque, transferência de saldo ou criação, edição e exclusão de destinos de saque salvos. Saques em moeda fiduciária por chave de API mantêm o acesso existente.
404not_foundId desconhecido, ou saque de outra conta — o 404 nunca confirma que o id existe.
409WITHDRAWAL_NOT_CANCELLABLECancelamento de saque que não está PENDING, ou linha que só o suporte cancela.
409(sem código estável)Quase-duplicado de criação sem chave em 90 s. Mande uma Idempotency-Key e repita.

#Dúvidas

P: Criei um saque e ele está preso em PENDING. R: Ele está aguardando aprovação — o despacho imediato só cobre o que a política da sua conta permite. Motivos comuns: auto-payout desligado na conta, tipo que não é pago automaticamente (por padrão só Pix), valor acima do teto do auto-payout ou a partir de manualApprovalThresholdCents (padrão R$ 15.001,00), a trava de saldo (autoWithdrawPct), o freio de velocidade ou uma regra que exige aprovação. Aprove pelo painel ou fale com o suporte sobre auto-payout.

P: Meu saque deu FAILED — a taxa foi cobrada? R: Não. O FAILED dispara um estorno automático e atômico de valor + taxa na carteira. O estorno cai assim que o lançamento na carteira acontece; quando a failure_message é "Estamos confirmando com o banco; você será avisado.", o estorno está retido (legs[].refunded é false) — o dinheiro ainda não voltou, aguarde o crédito e o aviso.

P: Meu saque ficou REVERSED — o dinheiro voltou para o meu saldo? R: Não necessariamente. O valor devolvido é creditado sem a taxa automaticamente só quando a sua marca habilita isso e a devolução não tem indício de fraude; senão, aguarda decisão. Confira legs[].reversalCredited e legSummary.pendingDecision.

P: Dá para cancelar um saque? R: Só enquanto estiver PENDING, com POST /v1/withdrawals/{id}/cancel ou o botão Cancelar saque no painel. Depois de aprovado, o dinheiro já está em trânsito.

P: Minha chave CNPJ foi recusada como inválida, mas meu banco aceita. R: O dígito verificador mod-11 é exigido e não existe parâmetro de bypass. Se a sua chave reprova na checagem, fale com o suporte.

P: Minha chave de telefone foi validada como CPF. R: Sem +55, um telefone de 11 dígitos parece CPF para a detecção automática. Mande o telefone como +55… ou declare type: "PHONE".

P: Meu banco não aparece na lista de TED. R: Usamos a lista de códigos FEBRABAN do Bacen. Abra um chamado se faltar algum — normalmente resolvemos em 24h.

#Identificadores de liquidação e consulta

charge.paid e payout.paid podem incluir data.object.settlement.end_to_end_id somente quando há E2E Pix BACEN válido. O identificador do provedor não é E2E. Em saques com múltiplas pernas, um identificador no topo não substitui os das pernas; o comprovante JSON expõe E2Es válidos das pernas concluídas quando há múltiplas pernas ativas. A ausência não significa falha do pagamento.

A retenção do webhook pago é opt-in por lojista. No modo estrito, o webhook não sai sem E2E e pode ficar retido indefinidamente se o provedor nunca o entregar; o crédito na carteira não espera. Restrinja o roteamento às rotas verificadas que entregam E2E para uma garantia prática. A liberação por prazo pode entregar sem E2E e não oferece a garantia estrita.

Consulte recursos criados pela sua integração em GET /v1/charges/by-idempotency-key/{key} ou GET /v1/withdrawals/by-idempotency-key/{key}, com a chave codificada em percent-encoding. Para /, ?, # ou espaços, use o filtro de lista ?idempotency_key= com codificação de URL. A lista tem zero ou um resultado na sua conta. A chave é obrigatória em cobranças; envie-a sempre no saque, pois saques sem chave não podem ser encontrados assim. Uma rejeição durante a reserva libera a chave do saque.

Consulta, cancelamento e comprovante de saque aceitam IDs po_ dos webhooks. GET /v1/withdrawals/{id}/receipt?format=json retorna dados para todos os estados terminais: completed, partially_completed, failed, rejected, cancelled e reversed. Apenas completed é comprovante de pagamento. Estados não terminais retornam 400. O padrão continua sendo o envelope PDF {contentType, base64}; Accept não seleciona o formato. Documentos são mascarados e a chave Pix aparece somente quando faltam nome e documento do recebedor.

#Exemplos de consulta

Nos exemplos, API_URL é a origem da API e API_KEY é a chave usada no servidor. A chave deve ter de 1 a 255 caracteres após remover espaços nas extremidades; uma chave vazia ou maior retorna 400. A rota por caminho retorna 404 neutro quando não há recurso da sua conta, inclusive quando outra conta usou a mesma chave. O filtro retorna o envelope normal da lista com zero ou um item.

bash
# API_URL é a origem da API; API_KEY é a chave usada no servidor.
curl "$API_URL/v1/charges/by-idempotency-key/order-42" \
  -H "Authorization: Bearer $API_KEY"
curl "$API_URL/v1/withdrawals/by-idempotency-key/order-42" \
  -H "Authorization: Bearer $API_KEY"

# A codificação de URL é especialmente útil para chaves com /, ?, # ou espaços.
curl --get "$API_URL/v1/charges" \
  -H "Authorization: Bearer $API_KEY" \
  --data-urlencode "idempotency_key=order/42 ?#"
curl --get "$API_URL/v1/withdrawals" \
  -H "Authorization: Bearer $API_KEY" \
  --data-urlencode "idempotency_key=order/42 ?#"

curl "$API_URL/v1/withdrawals/po_f6a7b8c9-d0e1-4234-9f01-234567890123/receipt?format=json" \
  -H "Authorization: Bearer $API_KEY"

#Contrato do comprovante JSON

A resposta abaixo ilustra um saque Pix concluído. Os valores usam a menor unidade da moeda (150000 equivale a R$ 1.500,00). Os textos do comprovante são em português, como no PDF; issuer e o rodapé dependem da marca e do desfecho.

json
{
  "object": "payout_receipt",
  "payout_id": "po_f6a7b8c9-d0e1-4234-9f01-234567890123",
  "status": "paid",
  "outcome": "completed",
  "is_payment_proof": true,
  "title": "Comprovante de Transferência PIX",
  "amount": 150000,
  "amount_label": "Valor transferido",
  "currency": "BRL",
  "transferred_at": "2026-10-28T14:33:02.000Z",
  "end_to_end_id": "E12345678202610281433Zx9Yw8Vu7Ts",
  "payer": { "name": "Example Store", "document": "12.345.678/****-**" },
  "receiver": { "name": "Maria da Silva", "document": "***.456.789-**", "pix_key": null },
  "refunded_to_balance": false,
  "what_happened": "A transferência foi concluída com sucesso.",
  "what_to_do": null,
  "reason": null,
  "legs": null,
  "issuer": "Example Brand",
  "footer": "O identificador E2E acima é registrado no Banco Central do Brasil e pode ser usado para confirmar esta transferência em qualquer instituição financeira participante do PIX.",
  "issued_at": "2026-10-28T15:00:00.000Z"
}
  • status é paid nos desfechos completed e partially_completed e failed em todos os outros — inclusive reversed. outcome identifica o estado terminal. Em particular, partially_completed tem status paid, mas is_payment_proof é false.
  • title e amount_label seguem a tabela de desfechos em Comprovante.
  • Para desfechos diferentes de completed, transferred_at e o end_to_end_id do topo são null. Mesmo em um saque concluído, end_to_end_id é null quando não há E2E Pix válido. what_to_do e reason podem ser null; reason é uma explicação segura para o cliente, sem erro bruto do provedor.
  • footer: com E2E, o texto do BACEN acima; pago sem E2E, "Comprovante emitido pela Liqfy com base nos registros da transferência."; nos demais, "Documento emitido pela Liqfy com base nos registros desta solicitação. NÃO é comprovante de pagamento."
  • refunded_to_balance reflete um crédito efetivo no ledger; não é deduzido do status. Os nomes e documentos em payer e receiver podem ser null; CPF/CNPJ são mascarados. receiver.pix_key só aparece preenchida quando faltam tanto o nome quanto o documento do recebedor.
  • legs é null quando há zero ou uma perna ativa. Com múltiplas pernas ativas, contém índice, valor, status em minúsculas e E2E; pernas substituídas são excluídas. Somente pernas concluídas podem ter E2E válido, inclusive em um saque parcialmente concluído; nas demais, o E2E é null. Por exemplo:
json
{
  "legs": [
  { "index": 0, "amount": 60000, "status": "completed", "end_to_end_id": "E12345678202610281433Zx9Yw8Vu7Ts" },
  { "index": 1, "amount": 90000, "status": "failed", "end_to_end_id": null }
  ]
}

#Exemplos nos SDKs

Os exemplos supõem que client já está configurado, payoutId / payout_id contém o ID do saque e key é a chave original de idempotência.

Node.js

ts
const receipt = await client.payouts.receipt(payoutId, { format: 'json' });
const charge = await client.charges.retrieveByIdempotencyKey(key);
const payout = await client.payouts.retrieveByIdempotencyKey(key);
const charges = await client.charges.list({ idempotency_key: key });
const payouts = await client.payouts.list({ idempotency_key: key });

Python

python
receipt = client.payouts.receipt(payout_id, format="json")
charge = client.charges.retrieve_by_idempotency_key(key)
payout = client.payouts.retrieve_by_idempotency_key(key)
charges = client.charges.list(idempotency_key=key)
payouts = client.payouts.list(idempotency_key=key)

PHP

php
$receipt = $client->payouts->receipt($payoutId, ['format' => 'json']);
$charge = $client->charges->retrieveByIdempotencyKey($key);
$payout = $client->payouts->retrieveByIdempotencyKey($key);
$charges = $client->charges->list(['idempotency_key' => $key]);
$payouts = $client->payouts->list(['idempotency_key' => $key]);

.NET

csharp
var receipt = await client.Payouts.ReceiptJsonAsync(payoutId);
var charge = await client.Charges.RetrieveByIdempotencyKeyAsync(key);
var payout = await client.Payouts.RetrieveByIdempotencyKeyAsync(key);
var charges = await client.Charges.ListByIdempotencyKeyAsync(key);
var payouts = await client.Payouts.ListByIdempotencyKeyAsync(key);