Retiro vía PIX (cash-out) con seguridad: clave, código QR y lista blanca de IP
Cómo implementar pagos de salida con la API Pagnovo — cash-out por clave PIX o QR, lista blanca de IP, permisos, restricción de receptor y eventos de devolución.
Equipo Pagnovo · 2026-08-08
Recibir dinero es relativamente seguro: en el peor caso, el pago no ocurre. Enviar dinero es otra historia — un error aquí significa fondos saliendo de tu cuenta hacia el destino equivocado, muchas veces sin vuelta.
Esta guía cubre el cash-out de la API Pagnovo con el foco correcto: seguridad primero.
Referencia completa: portal.pagnovo.com/docs.
Dos formas de enviar
Por clave PIX (DICT)
POST /withdraws/cash-out
{
"amount": 50000,
"pixKey": "destinatario@email.com",
"restrictReceiverDocument": true
}
Por código QR
POST /withdraws/cash-out/qrc
Úsalo cuando el destinatario presenta un QR (un cobro de proveedor, por ejemplo) en vez de una clave.
Y para hacer seguimiento:
GET /withdraws/collect/:id → estado y ciclo de vida del retiro
Las tres capas de protección
Lo que separa una integración de cash-out segura de un accidente esperando ocurrir:
1. Lista blanca de IP
Pagnovo permite restringir por IP las operaciones más sensibles — createWithdraw y
createRefund. Con la lista configurada, una llamada desde cualquier otra IP recibe:
400 "IP unauthorized"
Eso significa que, incluso si tu clave se filtra, no puede mover dinero fuera de tus servidores. Es la protección con mejor costo-beneficio de toda la integración — configúrala antes de ir a producción.
Lista vacía = sin restricción. No la dejes así en producción.
2. Permisos mínimos en la clave
Cada clave lleva permisos: la lectura exige view<Recurso>, la escritura manage<Recurso>. Sin el
permiso, la respuesta es 403 "Unauthorized Permissions".
Aprovéchalo: la clave que tu checkout usa para crear cobros no necesita poder retirar. Separa las claves por función. Si la del front se filtra, el daño es acotado.
3. restrictReceiverDocument
{ "restrictReceiverDocument": true }
Con esta bandera, el retiro solo se concreta si el CPF/CNPJ del titular de la clave coincide con lo esperado. Protege contra el escenario clásico de fraude: alguien cambia la clave PIX registrada y el dinero sale hacia otra persona.
Si pagas a proveedores o haces transferencias, úsala siempre.
El ciclo de vida y sus eventos
A diferencia del cash-in, el retiro tiene un estado más que necesita tratamiento:
| Evento | Significado | Qué hacer |
|---|---|---|
cashout.success |
Retiro concluido | Registrarlo en tu sistema |
cashout.failed |
Falló | Investigar, notificar, permitir reintento |
cashout.returned |
Devuelto | Reacreditar e investigar |
El cashout.returned es el que suele faltar en las integraciones. El dinero salió, fue rechazado
por el destino (cuenta cerrada, clave inválida, devolución) y volvió. Si tu sistema no trata este
evento, el saldo vuelve pero tu registro sigue diciendo "pagado" — y la diferencia solo aparece en la
conciliación, días después.
El saldo que realmente puedes retirar
Antes de disparar un retiro, revisa el saldo — y léelo con atención:
GET /accounts/balance
La respuesta separa disponible, en liquidación, depósitos de garantía, bloqueos cautelares y reservas de contracargo. Solo el disponible puede retirarse.
Sumar todo e intentar retirar el total es camino garantizado al fallo — y peor, a prometerle a tu usuario un retiro que no va a ocurrir.
Idempotencia: crítica en cash-out
En cobros, un webhook duplicado genera trabajo repetido. En retiros, una petición duplicada genera dinero enviado dos veces.
Protégete antes de llamar a la API:
// Bloquea ANTES de disparar
const lock = await db.withdrawLocks.insertIfAbsent({
key: `retiro:${usuarioId}:${solicitudId}`,
});
if (!lock) throw new Error('Retiro ya solicitado');
const retiro = await pagnovo.post('/withdraws/cash-out', {
amount, pixKey, restrictReceiverDocument: true,
});
await db.withdraws.save({ ...retiro, traceId: response.headers['x-trace-id'] });
Reglas prácticas:
- Bloquea por solicitud, no por usuario
- Nunca reintentes automáticamente un retiro cuya respuesta no recibiste — consulta primero
GET /withdraws/collect/:idy descubre qué pasó - Guarda el
x-trace-idde toda operación de salida; es lo que el soporte usa para rastrear
Aprobación humana para importes altos
No todo debe ser automático. Un patrón que evita perjuicios serios:
importe <= límite automático → procesa directo
importe > límite automático → cola de aprobación humana
Suma a eso límites por período (diario/mensual) y alertas para patrones atípicos — muchos retiros pequeños en secuencia, retiro justo después de cambiar la clave registrada, retiro hacia una clave nunca usada antes.
Probar en el sandbox
Con sk_test_* puedes ejercitar los caminos de fallo sin riesgo. Prueba explícitamente:
- Retiro exitoso →
cashout.success - Fallo →
cashout.failed - Devolución →
cashout.returned(el más olvidado) - Llamada desde IP no autorizada →
400 - Clave sin permiso de retiro →
403
Recuerda que el entorno de pruebas permite 30 operaciones por día (transacciones + retiros + reembolsos).
Checklist
- Lista blanca de IP configurada y no vacía
- Clave de retiro separada de la de cobro, con permisos mínimos
-
restrictReceiverDocumentactivo en transferencias - Bloqueo de idempotencia antes de la llamada
-
cashout.returnedtratado con reacreditación - Saldo leído por categoría (solo el disponible)
- Límite de importe con aprobación humana por encima del tope
-
x-trace-idguardado en toda operación - Caminos de fallo y devolución probados en el sandbox
Conoce nuestra API PIX, consulta la documentación de retiros y lee nuestra Política de Prevención de Fraude. Habla con el equipo.