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

Saque (payout) — o que a Liqfy responde quando não dá certo

Este documento fecha o contrato de falha de saque: o que você recebe, quando, e o que fazer com cada caso. Serve para você implementar uma vez e não precisar mexer de novo.

Há dois momentos em que um saque pode não acontecer, e eles se comportam de forma bem diferente. Não trate os dois no mesmo caminho de código. Um terceiro caso é mais raro: o saque pago e depois devolvido pela instituição de destino (veja a seção 4).


#1. Recusa imediata — o saldo da SUA conta não cobre

Acontece na hora, na resposta do POST /v1/withdrawals. Nenhum saque é criado, nenhum valor sai da sua carteira, e o idempotencyKey NÃO é gasto.

http
POST /v1/withdrawals
HTTP/1.1 400 Bad Request

{
  "error": {
    "type": "invalid_request_error",
    "code": "INSUFFICIENT_BALANCE",
    "message": "INSUFFICIENT_BALANCE",
    "details": { "available": 1000, "requested": 100005, "currency": "BRL" }
  },
  "request_id": "req_..."
}

Ramifique em error.code. A message é para log; o código é o contrato.

CampoO que é
error.codeSempre INSUFFICIENT_BALANCE neste caso. Constante estável.
details.availableSaldo gastável, na menor unidade (centavos no BRL).
details.requestedO que sairia da carteira: amount + taxa. Não é o amount que você mandou
details.currencyISO 4217 da carteira avaliada.

#Três coisas que evitam suporte depois

A taxa é cobrada POR CIMA. Para sacar amount, a carteira precisa de amount + taxa (o amountType padrão é "gross"). Por isso requested vem somado — sem ele você veria "pedi 1000, tenho 1000" e a recusa pareceria errada. (O modo legado amountType: "net" ainda é aceito no POST /v1/withdrawals — veja Criar o saque: o recebedor recebe amount − taxa, e aí requested é só o amount. Ele não é aceito em saque CRYPTO.)

Só available conta. É o saldo gastável. O pending (liquidado mas ainda retido) não entra na conta e nunca cobre um saque. Se a conta tiver mais de uma carteira na mesma moeda, available é o maior saldo entre elas, não a soma — a reserva sai de uma carteira só. Você pode fixar a carteira com o campo opcional walletId; sem ele, vale a carteira padrão da moeda quando tem saldo, senão qualquer carteira gastável com saldo suficiente.

A recusa não queima o idempotencyKey. Você pode depositar e reenviar com a MESMA chave; o saque é processado normalmente. (Isso valia para a recusa comum e passou a valer também para a corrida rara em que o saldo some entre a checagem e a reserva — corrigido em 19/08/2026.)

#Como não bater neste erro

GET /v1/withdrawals/payout-info?currency= — sem efeito colateral, feito para isso. Devolve { currency, available, pendingAdjustment, fee: { fixed, variablePct, minimum }, maxWithdrawable }: available é o saldo gastável, fee é o conjunto de parâmetros aplicado hoje e maxWithdrawable é o maior valor bruto que você pode pedir com a taxa já descontada — o número certo para o botão "sacar tudo". Contrato completo e exemplo: Cotação, taxa e saldo sacável.

#Outras recusas imediatas

A mesma regra vale para todo 4xx síncrono: nada é criado e nada sai da carteira.

HTTPCódigo / mensagemQuando
400erros de validaçãoO corpo não passa no esquema (ex.: type fora de PIX, TED, CRYPTO; amount que não é inteiro positivo).
400Idempotency-Key must be at most 255 charactersO cabeçalho Idempotency-Key tem mais de 255 caracteres.
4xxTENANT_KYC_REQUIREDO cadastro (KYC) da conta não está aprovado.
4xxrecusa por regra de saqueUma regra de saque configurada para a conta recusou o pedido. A recusa pode trazer ruleId e reasonKey (withdrawal.rule.pixKeyTypeNotAllowed ou withdrawal.rule.notAllowed).
403CRYPTO_PANEL_ONLYSaque CRYPTO enviado com chave de API — cripto é só pelo painel.
400INVALID_CRYPTO_WITHDRAWALSaque CRYPTO fora das regras (só BRL, mínimo R$ 5.000,00, sem net, endereço de carteira e cotação obrigatórios).

Toda recusa 4xx deixa rastro. Ela é registrada com valor, motivo e origem (API ou painel) e aparece na tela de saques e no log de auditoria (WITHDRAWAL_REJECTED). O mesmo pedido repetido com a mesma chave de idempotência e o mesmo código é registrado no máximo uma vez por hora. Um 5xx é falha nossa, não recusa, e não entra nesse registro.

#2. Recusa depois — o saque foi aceito e falhou no processamento

Aqui o POST respondeu 201 com status: "PENDING", e o valor já saiu da sua carteira (fica reservado). O desfecho chega depois, de duas formas:

  • pelo webhook payout.failed;
  • ou consultando GET /v1/withdrawals/{id} (po_... ou o id puro). A lista GET /v1/withdrawals?status=failed aceita as mesmas palavras minúsculas dos webhooks payout.*: pending, processing, paid, failed (failed = FAILED, REJECTED e CANCELLED). Valor desconhecido devolve 400.

Os campos que interessam:

CampoO que é
statusFAILED, REJECTED ou CANCELLED (failed no payout canônico)
failure_codeCódigo estável, minúsculo, legível por máquina. É por ele que você decide o fluxo. Vem quando o saque é FAILED.
failure_messageFrase neutra em português para aquele código. Pode ir para o seu usuário.
rejectionReasonTexto livre para log/suporte (reason no webhook legado). No FAILED o valor é a failure_message pública (desde 2026-10-21); em REJECTED/CANCELLED ainda pode trazer o texto técnico da instituição de destino. Não faça if em cima dele.
legs[].failureCodeSó em saque dividido em partes: o motivo de cada parte. Vocabulário fechado: INVALID_PIX_KEY, ACCOUNT_CLOSED, ACCOUNT_BLOCKED, KYC_REJECTED, LIMIT_REJECTED, DESTINATION_UNAVAILABLE, DESTINATION_NOT_ALLOWED, INSUFFICIENT_BALANCE, TIMEOUT, PROVIDER_CANCELLED, PROVIDER_REJECTED, UNKNOWN.

Onde cada um aparece:

Ondefailure_code / failure_messageTexto livre
GET /v1/withdrawals/{id} (e a lista)failure_code, failure_message na raizrejectionReason
payout.faileddata.object.failure_code, data.object.failure_messagenunca
json
{
  "id": "evt_...",
  "object": "event",
  "type": "payout.failed",
  "data": {
    "object": {
      "id": "po_...",
      "object": "payout",
      "status": "failed",
      "failure_code": "destination_unavailable",
      "failure_message": "O banco de destino não confirmou o PIX a tempo. Tente novamente mais tarde."
    }
  }
}

#Valores de failure_code

failure_codefailure_messageVale tentar de novo?
invalid_pix_keyA chave PIX de destino é inválida ou não foi encontrada.Só com outra chave
account_blockedA conta de destino está bloqueada para receber PIX.Só com outra conta
account_closedA conta de destino está encerrada ou inativa.Só com outra conta
limit_rejectedO valor excede um limite permitido para esta transferência.Sim, com valor menor
kyc_rejectedA transferência foi recusada na verificação cadastral do destinatário.Não
destination_unavailableO banco de destino não confirmou o PIX a tempo. Tente novamente mais tarde.Sim, mais tarde: o banco de destino não liquidou a tempo
destination_not_allowedO provedor não aceita esse tipo de destino; use outra chave Pix.Só com outro tipo de chave ou destino
payout_cancelledA transferência foi cancelada antes de ser concluída.Sim
provider_rejectedA transferência PIX foi recusada e não foi concluída.Sim, mas investigue antes

provider_rejected é o código genérico: o que não dá para atribuir com mais precisão cai nele. Trate um failure_code desconhecido como provider_rejected — códigos novos podem ser acrescentados; nenhum é renomeado ou removido.

Regra geral: um saque FAILED é definitivo e o valor volta automaticamente para a sua carteira; você não precisa pedir estorno. Quando o payout.failed chega depende de uma configuração da conta:

  • Só falhas definitivas (recomendado se você reembolsa o seu usuário ao receber payout.failed): todo payout.failed chega com o estorno já lançado, então você pode reembolsar o usuário na hora. Se o estorno precisar de confirmação com o banco, o payout.failed espera até ele ser lançado; se o PIX saiu, chega payout.paid no lugar. Peça ao suporte para ativar.
  • Padrão: o payout.failed é enviado na hora. Quase sempre o estorno já foi lançado. Quando não dá para provar que o PIX nunca saiu, o estorno fica retido para confirmação com o banco: a failure_message é "Estamos confirmando com o banco; você será avisado." e legs[].refunded é false. Não reembolse o usuário ainda: consulte GET /v1/withdrawals/{id} ou o extrato da carteira até o estorno cair. A liberação não gera outro webhook.

(A failure_message não promete estorno — quem prova é o lançamento na carteira.)

#Saque dividido em partes (pernas)

Cada PIX tem um teto (R$ 15.000,00 por padrão). Um saque acima dele é dividido em várias transferências PIX — as pernas —, cada uma cheia até o teto e o resto na última (R$ 110.000,00 → 7 × R$ 15.000,00 + 1 × R$ 5.000,00). Cada perna tem o seu próprio E2E e pode falhar sozinha.

Quando parte das pernas é paga e parte falha, o saque termina PARTIALLY_COMPLETED — paid no vocabulário público — e o valor das pernas que falharam volta para a sua carteira assim que o estorno cai. Olhe legs[] para saber exatamente o que aconteceu:

CampoO que é
legIndexNúmero da perna, começando em 1.
amountValor da perna, em centavos.
statusPENDING, PROCESSING, COMPLETED, FAILED ou REVERSED.
endToEndIdO E2E da perna, para conciliar com o extrato.
failureCodeVocabulário fechado acima; null quando a perna não falhou.
refundedSó em perna FAILED: true quando o estorno dela já caiu na carteira.
reversalCreditedSó em perna REVERSED: true quando o valor devolvido foi creditado para você (seção 4).
reversalEndToEndId, reversedAmount, reversedAtDados da devolução, em perna REVERSED.

Uma perna substituída por um reenvio nunca aparece em legs[]; o valor dela já está na perna que a substituiu.

#Cancelar um saque ainda PENDING

Enquanto o saque está PENDING (nada enviado ainda — por exemplo, aguardando aprovação), você mesmo pode cancelá-lo:

http
POST /v1/withdrawals/{id}/cancel
{ "reason": "opcional, até 500 caracteres" }

Responde 200 com o saque CANCELLED, e o valor volta para o saldo. Fora de PENDING responde 409 WITHDRAWAL_NOT_CANCELLABLE. Pelo painel, só o dono ou um admin da conta pode cancelar.


#3. Liquidez do processador

Quando o processador que faria o PIX está sem saldo para pagar, isso não é culpa sua. Hoje o saque chega para você como FAILED com failure_code: "provider_rejected", e o valor volta automaticamente para a sua carteira assim que o lançamento na carteira cai (o estorno retido também vale aqui — veja a regra geral acima). Não há retentativa automática nem janela de espera: se quiser, peça o saque de novo — de preferência um pouco depois.

O que fazer: enquanto o saque estiver PROCESSING, não repita o pedido. Um novo POST criaria um segundo saque e debitaria a carteira de novo. Mostre ao seu usuário algo como "pagamento em processamento" e aguarde o webhook final.

O PROCESSING pode durar mais que o normal quando o resultado do envio do PIX ainda não é conhecido. Nesse caso o saque segue PROCESSING enquanto a Liqfy confirma o desfecho com o provedor: ele nunca falha e é estornado só porque o tempo passou — só com prova positiva de que o PIX não saiu.


#4. Pago e depois devolvido (REVERSED)

Um PIX já pago pode ser devolvido pela instituição de destino. O saque (ou a perna) passa a REVERSED. Esse status não tem palavra no vocabulário público dos payout.*: para listá-lo, filtre com ?status=REVERSED.

O dinheiro devolvido não vai direto para o seu saldo. Depois que a devolução é confirmada no extrato do provedor, ele entra numa caixa de reversões da Liqfy e só é creditado para você automaticamente quando a política da marca permite e a devolução não tem sinal de fraude ou suspeita; caso contrário, uma pessoa decide — creditar para você ou reter. Até lá, legs[].reversalCredited é false: trate como "ainda não voltou para o saldo".


#Resumo para quem vai implementar

text
POST /v1/withdrawals
├── 400 INSUFFICIENT_BALANCE ....... sua carteira não cobre. Nada foi criado.
│                                    → cheque /v1/withdrawals/payout-info antes
├── outros 4xx ..................... recusado na entrada. Nada foi criado.
└── 201 PENDING .................... aceito, valor reservado
    ├── ainda PENDING .............. dá para cancelar: POST /v1/withdrawals/{id}/cancel
    ├── segue PROCESSING ........... NÃO repita. Aguarde o webhook.
    ├── PARTIALLY_COMPLETED (paid) . parte das pernas paga, as que falharam
    │                                voltam (legs[].refunded diz quais já caíram)
    ├── REVERSED ................... pago e devolvido; crédito só quando
    │                                legs[].reversalCredited = true
    └── webhook payout.failed
        └── failure_code = invalid_pix_key | account_* | limit_rejected |
            kyc_rejected | destination_unavailable | destination_not_allowed |
            payout_cancelled |
            provider_rejected (genérico / desconhecido)
            → definitivo; o estorno é automático assim que o lançamento
              na carteira cai. Se a failure_message for "Estamos confirmando
              com o banco; você será avisado." o estorno está retido
              (legs[].refunded = false): consulte o saque ou o extrato
              até cair. Não acontece com "só falhas definitivas".
              Repetir só depois de corrigir a causa (ex.: chave PIX), ou mais
              tarde no destination_unavailable.

Idempotência: mande sempre o cabeçalho Idempotency-Key no POST /v1/withdrawals (o campo idempotencyKey no corpo é fallback; o cabeçalho prevalece; no máximo 255 caracteres). É a sua proteção contra saque duplicado em qualquer retentativa de rede — repetir a mesma chave devolve o saque original em vez de criar outro. Se uma requisição deu timeout e você não sabe se o saque foi criado, consulte pela chave: GET /v1/withdrawals/by-idempotency-key/{key} ou GET /v1/withdrawals?idempotency_key={key}.