Skip to content
LIQFYdocs
PTENENEN
Go to dashboard

Payouts (Retiros / Saque)

El término de producto es Saque (payout); el endpoint actual /v1/withdrawals es el nombre de wire format en uso hoy y aún no se ha renombrado a /v1/payouts.

Mueve saldo fuera de tu billetera de Liqfy hacia un destino del mundo real: una clave PIX (Brasil) o una cuenta bancaria (TED). Es lo inverso de pagos PIX: los pagos acreditan tu billetera y los payouts la debitan.

Consulta por número de pedido: envía ese número como Idempotency-Key al crear el payout. Después usa GET /v1/withdrawals/by-idempotency-key/{numero-de-pedido} con una clave API de la misma cuenta. Un payout creado sin esta clave no puede consultarse por número de pedido.

Comprobante: GET /v1/withdrawals/{id}/receipt?format=json devuelve datos estructurados. Sin format, la respuesta sigue siendo el PDF en el sobre {contentType, base64}.

El callback payout.paid puede incluir el E2E Pix en data.object.settlement.end_to_end_id una vez confirmado. Para payouts divididos, consulta los E2E de settlement.legs[]; la ausencia de E2E no significa que el payout haya fallado.

Las modalidades son por cuenta. TED depende de que el módulo correspondiente esté habilitado para ti. Confírmalo con tu contacto de Liqfy antes de construir sobre alguna de ellas.

#Flujo de un vistazo

text
1. POST /v1/withdrawals/validate-pix-key   (optional — pre-flight)
2. POST /v1/withdrawals                    (creates the request and reserves the funds right away)
3. Liqfy dispatches — automatically or after approval
4. Webhook payout.paid (or payout.failed) hits your endpoint

Los payouts usan la misma API key que los cobros — Authorization: Bearer <key>, llamada desde tu servidor, nunca desde el navegador o la app del cliente final. (El header apikey todavía se acepta.) Las solicitudes con API key también están sujetas a la lista de IPs permitidas de tu cuenta. La billetera se debita al crear; si el payout además requiere aprobación depende de la configuración de tu cuenta.

Envía siempre el header Idempotency-Key al crear — es el canal principal de idempotencia (consulta Crear el payout).

#1. Validar una clave PIX (recomendado)

Antes de crear el payout, verifica que la clave de destino esté bien formada.

POST /v1/withdrawals/validate-pix-key

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

Respuesta 201 Created

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

Valores de type admitidos

TipoFormato
CPF11 dígitos (se eliminan puntos y guiones), checksum mod-11. Se rechazan los números con todos los dígitos iguales.
CNPJ14 dígitos (se eliminan puntos, guiones y barras), checksum mod-11. Se rechazan los números con todos los dígitos iguales.
EMAILForma local@domain.tld, en minúsculas, máx. 77 caracteres.
PHONE+55 seguido de 10 u 11 dígitos (+5511999999999). Se eliminan espacios, guiones y paréntesis.
RANDOMClave aleatoria — UUID v4, en minúsculas.
(omitido)Se detecta automáticamente a partir del valor de la clave.

El tipo no distingue mayúsculas de minúsculas. Un tipo conocido declarado se valida estrictamente — declarar CPF para un e-mail falla. Declarar cualquier otro tipo (por ejemplo EVP) se ignora: la clave se detecta automáticamente y la respuesta informa el tipo detectado.

Orden de detección automática: UUID v4 → RANDOM; empieza con +55 → PHONE; forma de e-mail → EMAIL; 11 dígitos → CPF; 14 dígitos → CNPJ. Por lo tanto, un número de teléfono enviado sin +55 (11 dígitos) se detecta como CPF — envía los teléfonos en E.164 o declara PHONE. Cuando nada coincide, la respuesta es isValid: false con type: "CPF" como marcador de posición y error: "Unable to detect PIX key type".

El endpoint responde 201 incluso cuando la clave es inválida — un fallo vive en el cuerpo, nunca en el status HTTP. Solo un key ausente o vacío es un 400.

Fallo 201 Created

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

Valores posibles 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 typefalló la detección automática
PIX key must not be emptyclave con solo espacios en blanco

#2. Cotización, comisión y saldo retirable

Tres helpers de solo lectura, con la misma API key que el endpoint de creación. currency es opcional en todos ellos (por defecto BRL).

#Cotización — qué enviar para que el destinatario reciba un monto exacto

POST /v1/withdrawals/quote — cuerpo { value, currency? }, value un entero mayor que cero. Tarificado como PIX. Cuerpo inválido → 400.

Respuesta 201 Created

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

total es el bruto que pagas para que el destinatario reciba exactamente value — la comisión se suma al bruto para que el destino nunca la absorba.

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

#Comisión — lo que cobraría una creación

GET /v1/withdrawals/payout-fee

QueryObligatorioDescripción
amountsíEntero, de 1 a 10.000.000.000 (unidad mínima).
currencynoPor defecto BRL. De 3 a 8 caracteres.
typenoPIX (por defecto) o TED.

Cualquier valor fuera de estos límites → 400.

Respuesta 200 OK

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

fee es lo que POST /v1/withdrawals cobraría por ese monto (ya incluye el complemento de los tramos divididos por encima del tope 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 retirable y parámetros de comisión

GET /v1/withdrawals/payout-info — sin efectos secundarios, pensado para el botón de "retirar todo".

Respuesta 200 OK

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

available es el saldo disponible para gastar. maxWithdrawable es el mayor monto bruto que se puede solicitar con la comisión ya descontada. fee es el conjunto de parámetros aplicado hoy (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. Crear el payout

POST /v1/withdrawals

Idempotencia. Envía el header Idempotency-Key — el canal principal, con espacios recortados, de 255 caracteres como máximo (un header más largo → 400 "Idempotency-Key must be at most 255 characters"). El campo del cuerpo idempotencyKey (de 1 a 255 caracteres) es un respaldo para quienes no usan el SDK; cuando ambos están presentes, gana el header. La misma clave devuelve el mismo payout: 201 de nuevo, sin un segundo débito. Si la reserva falla, el registro se marca REJECTED y la clave se libera — después de recargar saldo puedes reintentar con la misma clave.

Sin clave, la única protección es una salvaguarda contra casi duplicados: los mismos type, amount y pixKey dentro de 90 segundos responden 409 sin un error.code estable. Envía una clave de idempotencia y nunca dependerás de ella.

CampoTipoObligatorioDescripción
typeenumsíPIX o TED. (CRYPTO existe pero es solo del dashboard — ver abajo.)
amountintegersíEntero positivo, unidad mínima de la moneda. En el modo por defecto gross es lo que recibe el destinatario; la comisión se debita aparte.
currencystringnoPor defecto BRL.
pixKeystringen PIXClave PIX de destino.
pixKeyTypeenumnoCPF, CNPJ, EMAIL, PHONE, RANDOM. Omitido o desconocido (por ejemplo EVP): se ignora y la clave se detecta automáticamente.
bankCodestringen TEDCódigo del banco (FEBRABAN).
bankAgencystringen TEDSucursal (agência).
bankAccountstringen TEDNúmero de cuenta.
walletIdstringnoBilletera OPERATIONAL disponible explícita en currency. Omitido → selección automática (la billetera por defecto de la moneda si tiene saldo; si no, cualquier billetera disponible con saldo suficiente). Desconocida → WALLET_NOT_FOUND; no disponible → WALLET_NOT_SPENDABLE; ninguna cubre el débito → INSUFFICIENT_BALANCE.
amountTypeenumnogross (por defecto) o net. net es legado y todavía se acepta: el débito es amount y el destinatario recibe amount − fee (fee >= amount → 400).
idempotencyKeystringnoRespaldo — prefiere el header Idempotency-Key.
totpCodestringnoSolo dashboard: el código 2FA cuando la cuenta exige 2FA en los retiros. Se ignora en solicitudes con API key.

Las claves desconocidas (description, cryptoAddress, cryptoNetwork, cryptoCurrency, cualquier otra) se eliminan silenciosamente — la API nunca las almacena ni las devuelve. cryptoAddress, cryptoNetwork y cryptoCurrency todavía pueden aparecer como campos legados de solo lectura en la respuesta; no son entradas de creación.

Los payouts cripto son solo del dashboard. type: "CRYPTO" (con cryptoWalletAddressId y cryptoEstimateId) se acepta solo de un usuario del dashboard con sesión iniciada; con una API key responde 403 con el token CRYPTO_PANEL_ONLY. Lo mismo para POST /v1/withdrawals/crypto/estimate; GET /v1/withdrawals/crypto/availability responde { "enabled": false } a las API keys. En el dashboard un payout cripto debe estar en BRL, desde R$ 5.000,00 (500000), no net, con ambos ids (de lo contrario 400 INVALID_CRYPTO_WITHDRAWAL), y siempre pide el código 2FA.

Ejemplo — payout PIX

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

Ejemplo — payout TED (los datos bancarios son tres strings planos)

bash
curl -X POST https://api.liqfy.com.br/v1/withdrawals \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: payout-2026-W17-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: 'payout-2026-W17' },
);
// 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).

Respuesta 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": "payout-2026-W17",
  "createdAt": "2026-04-25T16:10:00.000Z",
  "updatedAt": "2026-04-25T16:10:00.000Z"
}

La billetera se debita al crear. En el modo gross (el predeterminado) el débito es amount + fee (aquí 50.100) y el destinatario recibe exactamente amount — por eso netAmount es igual a amount. Los fondos quedan reservados desde este momento; la aprobación solo decide cuándo se despacha el payout.

Un 201 ya puede volver como APPROVED o PROCESSING: el auto-payout aprueba y despacha de inmediato cuando la política de tu cuenta lo permite. No está garantizado que "siempre empieza PENDING".

Cuando un payout no se concreta. El contrato completo de rechazo — el error.code para un rechazo inmediato por saldo, los códigos de fallo (failure_code), y cuáles de ellos se reembolsan automáticamente — está en Fallos de payout.

#Crear desde el dashboard

  • Solo el propietario de la cuenta o un admin puede crear un payout en el dashboard (un miembro normal recibe 403, code = WITHDRAWAL_CANCEL_ROLE_REQUIRED).
  • Cuando la cuenta exige 2FA en los retiros (siempre, para cripto), la solicitud necesita un totpCode reciente. Un usuario sin 2FA habilitado recibe 403 ("Esta conta exige 2FA para saques…"); un código ausente o inválido recibe 401 ("Código 2FA inválido ou ausente.").
  • Los payouts creados en el dashboard tienen source: "PANEL"; vía API key, source: "API".

#Las solicitudes rechazadas dejan rastro

Cada rechazo 4xx de POST /v1/withdrawals (saldo, KYC, validación, límites, reglas…) se registra en el log de auditoría de la cuenta como WITHDRAWAL_REJECTED y aparece en la pantalla de retiros, con monto, origen y motivo — por ejemplo "Pedido de saque de R$ 500,00 recusado: saldo insuficiente (disponível R$ 120,00), via API". La misma solicitud repetida con la misma clave de idempotencia y el mismo código de rechazo se registra una vez por hora. Los rechazos por la lista de IPs permitidas ocurren antes de este paso y no se registran aquí.

#4. Ciclo de vida

text
PENDING ─▶ APPROVED ─▶ PROCESSING ─▶ CONFIRMING ─▶ COMPLETED            ✓ funds delivered
                                                    └▶ PARTIALLY_COMPLETED   delivered in parts

Los resultados terminales también incluyen FAILED, REJECTED, CANCELLED y REVERSED. Diez status en total; los seis terminales (COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED, CANCELLED, REVERSED) nunca vuelven a cambiar.

StatusEfecto en la billetera
PENDINGDebitado al crear (monto + comisión reservados)
APPROVEDReservado — aprobado, esperando despacho
PROCESSINGReservado — enviado a liquidación
CONFIRMINGReservado — esperando la confirmación de la institución receptora
COMPLETEDDebitado (terminal) — entregado
PARTIALLY_COMPLETEDDebitado (terminal) — algunos tramos entregados, otros fallidos (ver legs); los tramos fallidos se reembolsan
FAILEDReembolsado automáticamente — el despacho falló (failure_code). El reembolso puede retenerse hasta confirmarse (ver Fallos de payout)
REJECTEDReembolsado — rechazado en la revisión, o por la reserva (system:reserve-failed)
CANCELLEDReembolsado (monto + comisión) — cancelado por ti antes del despacho, o cuando un admin desactiva la cuenta (motivo "Cancelado pelo admin; valor devolvido ao saldo")
REVERSEDEntregado y luego devuelto por el destinatario (reversalReason, reversalAmount, reversalStatus). El monto devuelto se acredita automáticamente solo cuando la marca lo habilita — ver Payouts devueltos

REJECTED no es solo "denegado por un admin": una creación cuya reserva falla también se marca REJECTED, con los fondos devueltos.

El despacho incierto permanece PROCESSING. Cuando la llamada al proveedor excede el tiempo de espera, el PIX puede haber salido o no. El payout (o el tramo) permanece PROCESSING mientras Liqfy confirma con el proveedor — solo se marca como fallido y se reembolsa con evidencia positiva de que el PIX no salió. Cuando esa verificación lo prueba, el mensaje de fallo es "Saque não efetivado pelo provedor. O valor foi devolvido ao saldo."

#Campos de respuesta más allá del cuerpo de creación

GET /v1/withdrawals/{id}, la lista y la respuesta de cancelación añaden:

CampoQué es
legs[]{ id, legIndex, amount, status, endToEndId, failureCode, refunded, reversalCredited, reversalEndToEndId, reversedAmount, reversedAt, createdAt, updatedAt }. Un payout PIX por encima del tope por transacción se divide en N tramos al aprobarse; legIndex empieza en 1. Los tramos reemplazados por un re-despacho interno no se listan.
legSummaryAgregado sobre legs: { count, paidOut, returned, awaitingReturn, pendingDecision, inFlight, total }.
recipientNameNombre del titular de la cuenta informado por el proveedor en la liquidación (prefiere un tramo completado), cuando está disponible.
reversalReason, reversalAmount, reversalStatusPresentes cuando el payout fue revertido.

Campos del tramo

  • status — PENDING, PROCESSING, COMPLETED, FAILED o REVERSED.
  • endToEndId — el E2E PIX propio del tramo, para conciliar con los extractos bancarios.
  • failureCode — de un conjunto cerrado, nunca texto del proveedor: 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 cuando el tramo no falló.
  • refunded — solo para un tramo FAILED: true una vez que el crédito del reembolso realmente existe en el ledger (reembolso del tramo o reembolso total del payout). false significa que el reembolso sigue retenido.
  • reversalCredited — solo para un tramo REVERSED: true una vez que el monto devuelto se acreditó a tu saldo.
  • reversalEndToEndId, reversedAmount, reversedAt — detalles de la devolución, para un tramo REVERSED.

legSummary

CampoSuma de
paidOutTramos COMPLETED — lo que llegó al destino.
returnedTramos FAILED ya reembolsados + tramos REVERSED ya acreditados. Solo montos de tramos: cuando se reembolsa todo el payout, la comisión devuelta no se incluye aquí.
awaitingReturnTramos FAILED cuyo reembolso aún no se ha hecho efectivo.
pendingDecisionTramos REVERSED cuyo monto devuelto no se te ha acreditado.
inFlightTramos PENDING / PROCESSING.
totalTodos los tramos — igual al netAmount del payout.

#Payouts devueltos

Cuando un destinatario devuelve un PIX pagado, el tramo pasa a REVERSED una vez que la devolución se confirma en el extracto del proveedor (solo se acepta la devolución del monto total del tramo). El payout pasa a REVERSED cuando todos los tramos activos fueron devueltos; con algunos tramos devueltos y otros no, el payout mantiene su status y los tramos devueltos muestran REVERSED.

El dinero devuelto vuelve a Liqfy, no directamente a tu billetera. Se acredita a tu saldo sin la comisión automáticamente solo cuando tu marca lo habilita y la devolución no está marcada como fraude o sospecha (códigos BE08, FR01); de lo contrario, una persona decide acreditarlo o retenerlo. Observa legs[].reversalCredited y legSummary.pendingDecision.

reversalReason usa estas etiquetas para los códigos de devolución del BACEN:

CódigoEtiqueta
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
otroDevolução PIX (<code>), o Devolução PIX sin código

#Cancelar un payout pendiente

POST /v1/withdrawals/{id}/cancel — cuerpo opcional { "reason": "..." } (con espacios recortados, hasta 500 caracteres; por defecto "Cancelado pelo lojista."). La misma autenticación que el endpoint de creación. Acepta el id simple o el id po_.

  • Solo mientras el payout está PENDING (nada despachado aún). Pasa a CANCELLED, amount + fee vuelven a la billetera en la misma transacción, y el resultado dispara los webhooks de abajo. Devuelve 200 con el payout (formato de consulta).
  • Ya aprobado, despachado o terminal → 409, code = WITHDRAWAL_NOT_CANCELLABLE. Los registros que solo nuestro equipo de soporte puede cancelar (origen MANUAL, por encima del umbral de aprobación manual, no BRL) también devuelven 409: cancélalos a través de soporte.
  • En el dashboard, solo el propietario de la cuenta o un admin puede cancelar (un miembro normal recibe 403, code = WITHDRAWAL_CANCEL_ROLE_REQUIRED). Vía API, basta con la clave de la cuenta. La cuenta recibe un e-mail "Withdrawal cancelled".
  • Payout de otra cuenta → 404 — y el 404 nunca confirma que el id exista.
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": "Duplicate request" }'
ts
await client.payouts.cancel('f6a7b8c9-d0e1-4234-9f01-234567890123', { reason: 'Duplicate request' });

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

#Comprobante

GET /v1/withdrawals/{id}/receipt — no solo para COMPLETED: el documento existe para todos los status terminales (COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED, CANCELLED, REVERSED). No terminal → 400. format opcional: pdf (por defecto) o json; cualquier otro valor → 400.

Respuesta 200 OK

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

Solo COMPLETED se titula "Comprovante de Transferência PIX"; los demás documentos registran el resultado e indican que no son un comprobante de pago:

StatusTítuloEtiqueta del monto
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

El documento menciona que el monto volvió a tu saldo solo cuando ese crédito existe en el ledger — un payout cancelado sin reembolso nunca lo afirma. El E2E del BACEN aparece solo en los documentos pagados; el id de transacción del proveedor nunca aparece. CPF/CNPJ se enmascaran; la clave PIX de destino se muestra solo cuando faltan el nombre y el documento del receptor. Una línea de motivo aparece solo para motivos reconocidos como seguros para el cliente (clave inválida, datos del titular que no coinciden, límite, cuenta de destino no elegible, cancelación, indisponibilidad genérica); los mensajes crudos del proveedor nunca llegan al 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

Suscríbete a payout.created, payout.approved, payout.paid y payout.failed. Todos usan el sobre versionado evt_ y la firma con timestamp. Aprobación significa processing, antes de la liquidación. Consulta 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 el vocabulario público: pending, processing, paid, failed. FAILED, REJECTED y CANCELLED llegan todos como payout.failed con status: "failed".

payout.failed incluye data.object.failure_code y data.object.failure_message cuando están disponibles. Usa el código estable para decidir qué hacer. destination_not_allowed significa que el proveedor rechazó este tipo de destino; reintenta solo con otro tipo de clave Pix u otro destino. Consulta Fallos de payout.

#6. Listar tus payouts

GET /v1/withdrawals — la misma API key que el endpoint de creación.

QueryPor defectoDescripción
page1Número de página (entero; cualquier otra cosa → 400).
limit20Entero. Tope fijo de 100 — un valor mayor se reduce silenciosamente, sin error.
status—CSV de cualquiera de los dos vocabularios (abajo). Token desconocido → 400 ("Invalid withdrawal status: …").
source—API, PANEL, MANUAL o INDEFINIDO (registros sin origen). Cualquier otro valor se ignora silenciosamente y la lista no se filtra.
orderdescPor fecha de creación. asc para los más antiguos primero; cualquier otra cosa es desc.
idempotency_key—Clave exacta, de 1 a 255 caracteres después de recortar espacios (de lo contrario 400). Cero o un resultado.

Ambos vocabularios se aceptan en status, y los valores de wire en la respuesta son los internos en mayúsculas. Una palabra pública en minúsculas se expande a su conjunto; cualquier otro token se compara con el status interno, sin distinguir mayúsculas de minúsculas (PROCESSING = solo PROCESSING, mientras que processing = APPROVED, PROCESSING y CONFIRMING; reversed = REVERSED).

Filtro públicoValores internos de wire
pendingPENDING
processingAPPROVED, PROCESSING, CONFIRMING
paidCOMPLETED, PARTIALLY_COMPLETED
failedFAILED, REJECTED, CANCELLED
(ninguno)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(...).

Respuesta 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. Límites y reglas

  • Tope PIX por transacción — R$ 15.000,00 por defecto (una configuración de la plataforma). Por encima del tope el payout se divide en N legs al aprobarse: cada tramo se llena hasta el tope y el resto va en el último (R$ 110.000,00 → 7 × R$ 15.000,00 + 1 × R$ 5.000,00). Cada tramo es su propio PIX, con su propio E2E, y puede fallar de forma independiente.
  • Límites de retiro por cuenta — minWithdrawalAmount / maxWithdrawalAmount por solicitud, dailyPayoutMaxAmount / dailyPayoutMaxCount por día (null = sin límite). Fuera de los límites responde AMOUNT_BELOW_MINIMUM, AMOUNT_ABOVE_MAXIMUM o DAILY_PAYOUT_LIMIT_EXCEEDED.
  • La billetera debe cubrir monto + comisión — nunca hay débitos parciales; la solicitud se rechaza directamente.
  • Aprobación — los payouts iguales o superiores a manualApprovalThresholdCents (por defecto R$ 15.001,00 — un centavo por encima del tope PIX, para que cualquier PIX de un solo tramo hasta el tope pueda aprobarse automáticamente), todo payout no BRL y los registros enviados a revisión manual necesitan aprobación. El auto-payout es política de la cuenta (autoPayoutEnabled, autoWithdrawPct — umbral por porcentaje del saldo, por defecto 50, tipos de retiro elegibles, límite del retiro automático y si todos los retiros requieren aprobación manual) — pide a soporte que revise la tuya. Un payout elegible se despacha en segundos al crearse; un barrido cada 5 minutos vuelve a verificar los payouts que siguen PENDING con la misma política.
  • Freno de velocidad — una ráfaga inusual de payouts desde la cuenta, o varias cuentas pagando la misma clave PIX en una ventana corta, puede enviar un payout a aprobación manual en lugar del auto-payout. Nunca rechaza el payout.
  • Reglas de retiro — las reglas de la plataforma pueden rechazar un payout (el rechazo incluye ruleId y reasonKey: withdrawal.rule.pixKeyTypeNotAllowed o withdrawal.rule.notAllowed) o exigir aprobación para él.
  • TED — restringido a bancos brasileños (lista bankCode de FEBRABAN). Liquidación: el mismo día hábil si se aprueba antes de las 16:30 BRT.

#Códigos de error

Ramifica según error.code. El message es para tus logs; el código es el contrato. Excepción: algunos tokens (WALLET_NOT_FOUND, WALLET_NOT_SPENDABLE, TENANT_KYC_REQUIRED, CRYPTO_PANEL_ONLY) son texto plano del mensaje — sus filas muestran el fallback error.code, y debes buscar el token en error.message. Mapa completo: Errores.

HTTPerror.codeCuándo
400INSUFFICIENT_BALANCENinguna billetera cubre el débito (details: available, requested, currency). 400 en esta ruta — no 422.
400invalid_requestEl walletId explícito es desconocido, está inactivo, es de otra moneda o no está disponible para gastar. Token en error.message: WALLET_NOT_FOUND / WALLET_NOT_SPENDABLE.
400invalid_requestFalló la validación del cuerpo, Idempotency-Key con más de 255 caracteres, filtro status inválido o clave de idempotencia inválida en la búsqueda.
400AMOUNT_BELOW_MINIMUM / AMOUNT_ABOVE_MAXIMUMFuera de los límites de retiro de la cuenta.
422DAILY_PAYOUT_LIMIT_EXCEEDEDSe alcanzó el tope diario de monto o de cantidad de la cuenta.
403account_suspendedCuenta suspendida o bloqueada.
403forbiddenEl KYC de la cuenta no está activo (cuando se aplica la verificación KYC). Token en error.message: TENANT_KYC_REQUIRED.
403forbiddentype: "CRYPTO" (o la estimación cripto) llamado con una API key. Token en error.message: CRYPTO_PANEL_ONLY.
403WITHDRAWAL_CANCEL_ROLE_REQUIREDLa membresía en la cuenta no es OWNER/ADMIN: creación o cancelación de retiros, transferencia entre billeteras, o creación, actualización o eliminación de destinos de retiro guardados. Los payouts fiat con API key mantienen su acceso actual.
404not_foundId desconocido, o un payout de otra cuenta — el 404 nunca confirma que el id exista.
409WITHDRAWAL_NOT_CANCELLABLECancelación de un payout que no está PENDING, o un registro que solo soporte puede cancelar.
409(sin código estable)Casi duplicado en una creación sin clave dentro de 90 s. Envía un Idempotency-Key y reintenta.

#Preguntas frecuentes

P: Creé un payout pero está atascado en PENDING. R: Está esperando aprobación — el despacho inmediato solo cubre lo que permite la política de tu cuenta. Motivos comunes: el auto-payout está desactivado para la cuenta, el tipo no se paga automáticamente (por defecto solo PIX), el monto está por encima del techo de auto-payout o es igual o superior a manualApprovalThresholdCents (por defecto R$ 15.001,00), el umbral de saldo (autoWithdrawPct), el freno de velocidad o una regla que exige aprobación. Apruébalo desde el dashboard o consulta a soporte sobre el auto-payout.

P: Mi payout FAILED — ¿se me cobró la comisión? R: No. FAILED dispara un reembolso automático y atómico a la billetera de amount + fee. El reembolso se hace efectivo en cuanto ocurre el movimiento en la billetera; cuando failure_message es "Estamos confirmando com o banco; você será avisado.", el reembolso está retenido (legs[].refunded es false) — el dinero aún no ha vuelto; espera el crédito y el aviso.

P: Mi payout fue REVERSED — ¿el dinero volvió a mi saldo? R: No necesariamente. El monto devuelto se acredita sin la comisión automáticamente solo cuando tu marca lo habilita y la devolución no está marcada como fraude; de lo contrario, espera una decisión. Revisa legs[].reversalCredited y legSummary.pendingDecision.

P: ¿Puedo cancelar un payout? R: Solo mientras está PENDING, con POST /v1/withdrawals/{id}/cancel o con el botón Cancel withdrawal del dashboard. Una vez aprobado, los fondos están en tránsito.

P: Mi clave CNPJ fue rechazada como inválida aunque mi banco la acepta. R: El checksum mod-11 se aplica y no hay ningún parámetro para omitirlo. Si tu clave no pasa la verificación, contacta a soporte.

P: Mi clave de teléfono se validó como CPF. R: Sin +55, un teléfono de 11 dígitos parece un CPF para la detección automática. Envía el teléfono como +55… o declara type: "PHONE".

P: El banco de TED no aparece en mi lista desplegable. R: Usamos la lista de códigos FEBRABAN del Bacen. Abre un ticket si falta un código — normalmente se corrige en 24 h.

#Identificadores de liquidación y consulta

charge.paid y payout.paid pueden incluir data.object.settlement.end_to_end_id solo cuando existe un E2E Pix BACEN válido. El identificador del proveedor no es un E2E. En pagos con múltiples tramos, un identificador superior no sustituye a los de cada tramo; el comprobante JSON expone los E2E válidos de tramos completados cuando hay varios tramos activos. Su ausencia no significa que el pago haya fallado.

La retención del webhook pagado es opcional por comercio. En modo estricto no se entrega sin E2E y puede quedar retenido indefinidamente si el proveedor nunca lo aporta; el abono a la billetera no espera. Restrinja el enrutamiento a rutas verificadas que entreguen E2E para una garantía práctica. La liberación por plazo puede entregar sin E2E y no ofrece la garantía estricta.

Consulte recursos creados por su integración con GET /v1/charges/by-idempotency-key/{key} o GET /v1/withdrawals/by-idempotency-key/{key}, codificando la clave con percent-encoding. Para /, ?, # o espacios, use el filtro de lista ?idempotency_key= codificado como URL. La lista contiene cero o un resultado de su cuenta. La clave es obligatoria en cobros; envíela siempre en retiros, ya que sin ella no se pueden consultar así. Un rechazo durante la reserva libera la clave del retiro.

La consulta, cancelación y comprobante aceptan IDs po_ del webhook. GET /v1/withdrawals/{id}/receipt?format=json devuelve datos para todos los estados terminales: completed, partially_completed, failed, rejected, cancelled y reversed. Solo completed es comprobante de pago. Un estado no terminal devuelve 400. Por defecto sigue el sobre PDF {contentType, base64}; Accept no selecciona el formato. Los documentos se enmascaran y la clave Pix aparece solo si faltan el nombre y documento del destinatario.

#Ejemplos de consulta

En los ejemplos, API_URL es el origen de la API y API_KEY es la clave utilizada en el servidor. La clave debe tener entre 1 y 255 caracteres después de quitar los espacios de los extremos; una clave vacía o más larga devuelve 400. La ruta devuelve un 404 neutro cuando no hay un recurso de su cuenta, incluso si otra cuenta usó la misma clave. El filtro devuelve el sobre normal de lista con cero o un elemento.

bash
# API_URL es el origen de la API; API_KEY es la clave usada en el 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"

# La codificación de URL es especialmente útil para claves con /, ?, # o espacios.
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 del comprobante JSON

La respuesta siguiente ilustra un retiro Pix completado. Los importes usan la unidad mínima de la moneda (150000 equivale a R$ 1.500,00). Los textos del comprobante están en portugués, como en el PDF; issuer y el pie dependen de la marca y del resultado.

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 es paid para los resultados completed y partially_completed, y failed para cualquier otro resultado — incluido reversed. outcome identifica el resultado terminal. En particular, un resultado partially_completed tiene status paid pero is_payment_proof es false.
  • title y amount_label siguen la tabla de resultados de Comprobante.
  • footer: con un E2E, el texto del BACEN de arriba; pagado sin E2E, "Comprovante emitido pela Liqfy com base nos registros da transferência."; en otro caso, "Documento emitido pela Liqfy com base nos registros desta solicitação. NÃO é comprovante de pagamento."
  • Para resultados distintos de completed, transferred_at y el end_to_end_id superior son null. Incluso en un retiro completado, end_to_end_id es null si no hay un E2E Pix válido. what_to_do y reason pueden ser null; reason es una explicación segura para el cliente, no un error bruto del proveedor.
  • refunded_to_balance refleja un crédito real en el libro mayor; no se deduce del estado. Los nombres y documentos de payer y receiver pueden ser null; CPF/CNPJ se enmascaran. receiver.pix_key solo se completa cuando faltan tanto el nombre como el documento del destinatario.
  • legs es null cuando hay cero o un tramo activo. Con varios tramos activos, incluye índice, importe, estado en minúsculas y E2E; se excluyen los tramos sustituidos. Solo los tramos completados pueden contener un E2E válido, incluso en un retiro parcialmente completado; en los demás, el E2E es null. Por ejemplo:
json
{
  "legs": [
  { "index": 0, "amount": 60000, "status": "completed", "end_to_end_id": "E12345678202610281433Zx9Yw8Vu7Ts" },
  { "index": 1, "amount": 90000, "status": "failed", "end_to_end_id": null }
  ]
}

#Ejemplos en los SDK

Los ejemplos suponen que client ya está configurado, que payoutId / payout_id contiene el ID del retiro y que key es la clave original de idempotencia.

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);