Pular para o conteúdo
DEMO PAYdocs
PTEN
Ir para o painel

Saque (payout) — o que a Demo Pay 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.

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.


#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. Por isso requested vem somado — sem ele você veria "pedi 1000, tenho 1000" e a recusa pareceria errada. (Existe o modo legado amountType: "net", em que o recebedor recebe amount − taxa; aí requested é só o amount.)

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ó.

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-infosem efeito colateral, feito para isso. Devolve o available, a taxa aplicável e o maxWithdrawable: o maior valor que você pode pedir com a taxa já descontada. É o número certo para o botão "sacar tudo".

#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 withdrawal.failed;
  • ou consultando GET /v1/withdrawals/{id}.

Os campos que interessam:

CampoO que é
statusFAILED, REJECTED ou CANCELLED
providerErrorCodeCódigo estável, legível por máquina. É por ele que você decide o fluxo
rejectionReasonTexto livre para log/suporte. Não faça if em cima dele

#Códigos de providerErrorCode

CódigoSignificadoSua carteiraVale tentar de novo?
INVALID_PIX_KEYChave PIX inválida, inexistente ou bloqueadaEstornadaSó com outra chave
ACCOUNT_CLOSEDConta do recebedor encerradaEstornadaSó com outra conta
ACCOUNT_BLOCKEDConta do recebedor bloqueadaEstornadaSó com outra conta
KYC_REJECTEDRecebedor barrado na checagem do bancoEstornadaNão
LIMIT_REJECTEDEstourou limite do arranjo/bancoEstornadaNao
PROVIDER_REJECTEDRecusa genérica do processadorEstornadaSim, mas investigue antes
PROVIDER_INSUFFICIENT_BALANCELiquidez do processador — ver seção 3NÃO estornadaAutomático, não repita

Regra geral: em todos os códigos acima, menos o último, a falha é definitiva e o valor volta automaticamente para a sua carteira. Você não precisa pedir estorno; basta reagir ao webhook.


#3. PROVIDER_INSUFFICIENT_BALANCE — a exceção, e a janela de 24h

Este código não é culpa sua e não é falha do seu saque. Ele significa que o processador que faria o PIX estava momentaneamente sem liquidez. O saque continua válido.

Por isso ele é o único código transitório da tabela, e se comporta diferente:

  • o valor não é estornado — o saque fica retido, aguardando;
  • a Demo Pay re-tenta sozinha, sem você fazer nada;
  • a janela é de 24 horas contadas a partir da primeira recusa;
  • se o processador se recuperar dentro da janela, o saque segue normalmente e você recebe withdrawal.completed;
  • se as 24 horas passarem sem recuperação, aí sim ele vira FAILED com estorno automático para a sua carteira, e você recebe withdrawal.failed.

O que fazer: ao ver este código, 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. Só existem dois desfechos possíveis, e os dois chegam por webhook: completed ou failed.

Estado atual desta função. A retentativa de 24h está implementada e testada, mas ainda não ligada em produção — ela fica atrás de um interruptor que hoje está desligado. Enquanto estiver assim, uma recusa por liquidez do processador chega para você como PROVIDER_REJECTED, com estorno imediato (o comportamento definitivo da tabela).

Implemente o tratamento agora mesmo assim. O código PROVIDER_INSUFFICIENT_BALANCE já faz parte do contrato; no dia em que o interruptor for ligado, sua integração passa a receber o comportamento novo sem precisar de nenhuma alteração do seu lado. Avisaremos a data.


#Resumo para quem vai implementar

text
POST /v1/withdrawals
├── 400 INSUFFICIENT_BALANCE ....... sua carteira não cobre. Nada foi criado.
│                                    → cheque /v1/wallets/balance antes
└── 201 PENDING .................... aceito, valor reservado
    └── webhook withdrawal.failed
        ├── providerErrorCode = PROVIDER_INSUFFICIENT_BALANCE
        │   → NÃO repita. Retido, re-tentado por até 24h.
        │     Desfecho vem por webhook (completed ou failed).
        └── qualquer outro código
            → definitivo, carteira JÁ estornada.
              Repetir só depois de corrigir a causa (ex.: chave PIX).

Idempotência: mande sempre idempotencyKey no POST /v1/withdrawals. É a sua proteção contra saque duplicado em qualquer retentativa de rede — repetir o mesmo idempotencyKey devolve o saque original em vez de criar outro.