Payouts (Retiros / Saque)
El término de producto es Saque (
payout); el endpoint actual/v1/withdrawalses 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
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 endpointLos 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
{
"key": "12345678909",
"type": "CPF"
}Respuesta 201 Created
{
"isValid": true,
"type": "CPF",
"normalized": "12345678909"
}Valores de type admitidos
| Tipo | Formato |
|---|---|
CPF | 11 dígitos (se eliminan puntos y guiones), checksum mod-11. Se rechazan los números con todos los dígitos iguales. |
CNPJ | 14 dígitos (se eliminan puntos, guiones y barras), checksum mod-11. Se rechazan los números con todos los dígitos iguales. |
EMAIL | Forma 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. |
RANDOM | Clave 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
{
"isValid": false,
"type": "CPF",
"normalized": "12345678900",
"error": "CPF checksum invalid"
}Valores posibles de error
error | Tipo |
|---|---|
CPF must be 11 digits / CPF checksum invalid | CPF |
CNPJ must be 14 digits / CNPJ checksum invalid | CNPJ |
Invalid email format / Email exceeds 77 characters | EMAIL |
Phone must start with +55 / Phone must have 10 or 11 digits after +55 | PHONE |
Random key must be a valid UUID v4 | RANDOM |
Unable to detect PIX key type | falló la detección automática |
PIX key must not be empty | clave 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
{
"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.
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 }'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
| Query | Obligatorio | Descripción |
|---|---|---|
amount | sí | Entero, de 1 a 10.000.000.000 (unidad mínima). |
currency | no | Por defecto BRL. De 3 a 8 caracteres. |
type | no | PIX (por defecto) o TED. |
Cualquier valor fuera de estos límites → 400.
Respuesta 200 OK
{
"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.
curl "https://api.liqfy.com.br/v1/withdrawals/payout-fee?amount=50000¤cy=BRL&type=PIX" \
-H "Authorization: Bearer $LIQFY_API_KEY"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
{
"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).
curl "https://api.liqfy.com.br/v1/withdrawals/payout-info?currency=BRL" \
-H "Authorization: Bearer $LIQFY_API_KEY"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.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
type | enum | sí | PIX o TED. (CRYPTO existe pero es solo del dashboard — ver abajo.) |
amount | integer | sí | 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. |
currency | string | no | Por defecto BRL. |
pixKey | string | en PIX | Clave PIX de destino. |
pixKeyType | enum | no | CPF, CNPJ, EMAIL, PHONE, RANDOM. Omitido o desconocido (por ejemplo EVP): se ignora y la clave se detecta automáticamente. |
bankCode | string | en TED | Código del banco (FEBRABAN). |
bankAgency | string | en TED | Sucursal (agência). |
bankAccount | string | en TED | Número de cuenta. |
walletId | string | no | Billetera 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. |
amountType | enum | no | gross (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). |
idempotencyKey | string | no | Respaldo — prefiere el header Idempotency-Key. |
totpCode | string | no | Solo 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"(concryptoWalletAddressIdycryptoEstimateId) se acepta solo de un usuario del dashboard con sesión iniciada; con una API key responde403con el tokenCRYPTO_PANEL_ONLY. Lo mismo paraPOST /v1/withdrawals/crypto/estimate;GET /v1/withdrawals/crypto/availabilityresponde{ "enabled": false }a las API keys. En el dashboard un payout cripto debe estar enBRL, desde R$ 5.000,00 (500000), nonet, con ambos ids (de lo contrario400INVALID_CRYPTO_WITHDRAWAL), y siempre pide el código 2FA.
Ejemplo — payout PIX
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)
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"
}'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
{
"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.codepara 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
totpCodereciente. Un usuario sin 2FA habilitado recibe403("Esta conta exige 2FA para saques…"); un código ausente o inválido recibe401("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
PENDING ─▶ APPROVED ─▶ PROCESSING ─▶ CONFIRMING ─▶ COMPLETED ✓ funds delivered
└▶ PARTIALLY_COMPLETED delivered in partsLos 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.
| Status | Efecto en la billetera |
|---|---|
PENDING | Debitado al crear (monto + comisión reservados) |
APPROVED | Reservado — aprobado, esperando despacho |
PROCESSING | Reservado — enviado a liquidación |
CONFIRMING | Reservado — esperando la confirmación de la institución receptora |
COMPLETED | Debitado (terminal) — entregado |
PARTIALLY_COMPLETED | Debitado (terminal) — algunos tramos entregados, otros fallidos (ver legs); los tramos fallidos se reembolsan |
FAILED | Reembolsado automáticamente — el despacho falló (failure_code). El reembolso puede retenerse hasta confirmarse (ver Fallos de payout) |
REJECTED | Reembolsado — rechazado en la revisión, o por la reserva (system:reserve-failed) |
CANCELLED | Reembolsado (monto + comisión) — cancelado por ti antes del despacho, o cuando un admin desactiva la cuenta (motivo "Cancelado pelo admin; valor devolvido ao saldo") |
REVERSED | Entregado 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:
| Campo | Qué 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. |
legSummary | Agregado sobre legs: { count, paidOut, returned, awaitingReturn, pendingDecision, inFlight, total }. |
recipientName | Nombre del titular de la cuenta informado por el proveedor en la liquidación (prefiere un tramo completado), cuando está disponible. |
reversalReason, reversalAmount, reversalStatus | Presentes cuando el payout fue revertido. |
Campos del tramo
status—PENDING,PROCESSING,COMPLETED,FAILEDoREVERSED.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.nullcuando el tramo no falló.refunded— solo para un tramoFAILED:trueuna vez que el crédito del reembolso realmente existe en el ledger (reembolso del tramo o reembolso total del payout).falsesignifica que el reembolso sigue retenido.reversalCredited— solo para un tramoREVERSED:trueuna vez que el monto devuelto se acreditó a tu saldo.reversalEndToEndId,reversedAmount,reversedAt— detalles de la devolución, para un tramoREVERSED.
legSummary
| Campo | Suma de |
|---|---|
paidOut | Tramos COMPLETED — lo que llegó al destino. |
returned | Tramos 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í. |
awaitingReturn | Tramos FAILED cuyo reembolso aún no se ha hecho efectivo. |
pendingDecision | Tramos REVERSED cuyo monto devuelto no se te ha acreditado. |
inFlight | Tramos PENDING / PROCESSING. |
total | Todos 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ódigo | Etiqueta |
|---|---|
BE08 | Devolução por suspeita de fraude |
FR01 | Conta do recebedor sob suspeita de fraude |
MD06 | Devolução solicitada pelo pagador |
AC03 | Chave/conta do recebedor inválida |
AC04 | Conta do recebedor encerrada |
AC06 | Conta do recebedor bloqueada |
AM09 | Valor incorreto |
SL02 | Devolução por solicitação do participante |
DUPL | Pagamento em duplicidade |
| otro | Devoluçã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 aCANCELLED,amount + feevuelven a la billetera en la misma transacción, y el resultado dispara los webhooks de abajo. Devuelve200con 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 (origenMANUAL, por encima del umbral de aprobación manual, no BRL) también devuelven409: 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 el404nunca confirma que el id exista.
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" }'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
{
"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:
| Status | Título | Etiqueta del monto |
|---|---|---|
COMPLETED | Comprovante de Transferência PIX | Valor transferido |
PARTIALLY_COMPLETED | Transferência Parcialmente Concluída | Valor solicitado |
FAILED | Transferência Não Realizada | Valor solicitado |
REJECTED | Transferência Não Aprovada | Valor solicitado |
CANCELLED | Transferência Cancelada | Valor solicitado |
REVERSED | Transferência Devolvida | Valor 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.
curl https://api.liqfy.com.br/v1/withdrawals/f6a7b8c9-d0e1-4234-9f01-234567890123/receipt \
-H "Authorization: Bearer $LIQFY_API_KEY"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.
{
"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.
| Query | Por defecto | Descripción |
|---|---|---|
page | 1 | Número de página (entero; cualquier otra cosa → 400). |
limit | 20 | Entero. 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. |
order | desc | Por 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úblico | Valores internos de wire |
|---|---|
pending | PENDING |
processing | APPROVED, PROCESSING, CONFIRMING |
paid | COMPLETED, PARTIALLY_COMPLETED |
failed | FAILED, REJECTED, CANCELLED |
| (ninguno) | REVERSED |
curl "https://api.liqfy.com.br/v1/withdrawals?page=1&limit=20&status=paid&source=API&order=desc" \
-H "Authorization: Bearer $LIQFY_API_KEY"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
{
"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
legsal 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/maxWithdrawalAmountpor solicitud,dailyPayoutMaxAmount/dailyPayoutMaxCountpor día (null = sin límite). Fuera de los límites respondeAMOUNT_BELOW_MINIMUM,AMOUNT_ABOVE_MAXIMUMoDAILY_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 siguenPENDINGcon 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
ruleIdyreasonKey:withdrawal.rule.pixKeyTypeNotAllowedowithdrawal.rule.notAllowed) o exigir aprobación para él. - TED — restringido a bancos brasileños (lista
bankCodede 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.
| HTTP | error.code | Cuándo |
|---|---|---|
| 400 | INSUFFICIENT_BALANCE | Ninguna billetera cubre el débito (details: available, requested, currency). 400 en esta ruta — no 422. |
| 400 | invalid_request | El 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. |
| 400 | invalid_request | Falló 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. |
| 400 | AMOUNT_BELOW_MINIMUM / AMOUNT_ABOVE_MAXIMUM | Fuera de los límites de retiro de la cuenta. |
| 422 | DAILY_PAYOUT_LIMIT_EXCEEDED | Se alcanzó el tope diario de monto o de cantidad de la cuenta. |
| 403 | account_suspended | Cuenta suspendida o bloqueada. |
| 403 | forbidden | El KYC de la cuenta no está activo (cuando se aplica la verificación KYC). Token en error.message: TENANT_KYC_REQUIRED. |
| 403 | forbidden | type: "CRYPTO" (o la estimación cripto) llamado con una API key. Token en error.message: CRYPTO_PANEL_ONLY. |
| 403 | WITHDRAWAL_CANCEL_ROLE_REQUIRED | La 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. |
| 404 | not_found | Id desconocido, o un payout de otra cuenta — el 404 nunca confirma que el id exista. |
| 409 | WITHDRAWAL_NOT_CANCELLABLE | Cancelació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.
# 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.
{
"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"
}statusespaidpara los resultadoscompletedypartially_completed, yfailedpara cualquier otro resultado — incluidoreversed.outcomeidentifica el resultado terminal. En particular, un resultadopartially_completedtiene statuspaidperois_payment_proofesfalse.titleyamount_labelsiguen 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_aty elend_to_end_idsuperior sonnull. Incluso en un retiro completado,end_to_end_idesnullsi no hay un E2E Pix válido.what_to_doyreasonpueden sernull;reasones una explicación segura para el cliente, no un error bruto del proveedor. refunded_to_balancerefleja un crédito real en el libro mayor; no se deduce del estado. Los nombres y documentos depayeryreceiverpueden sernull; CPF/CNPJ se enmascaran.receiver.pix_keysolo se completa cuando faltan tanto el nombre como el documento del destinatario.legsesnullcuando 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 esnull. Por ejemplo:
{
"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
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
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
$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
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);