Conciliación de pagos automatizada: deja de cerrar la caja a mano
Cómo automatizar la conciliación con la API Pagnovo — externalId, consulta de transacciones, saldo por categoría, x-trace-id y la rutina que cierra el día sola.
Equipo Pagnovo · 2026-08-06
Conciliar responde una pregunta simple: ¿lo que el sistema dice que recibí coincide con lo que realmente entró? Hecho a mano, en una planilla, el costo aparece de tres formas — horas de trabajo, error humano y el descubrimiento tardío de pedidos pagados que nunca se entregaron.
Esta guía muestra cómo automatizarlo.
El error que vuelve difícil la conciliación después
La decisión más importante ocurre al crear el cobro, no al cierre de mes: enviar el externalId.
POST /transactions/v2/purchase
{
"amount": 18990,
"externalId": "pedido-1042",
"description": "Pedido #1042"
}
El externalId es el identificador del pedido en tu sistema. Sin él dependes de guardar el ID de
Pagnovo en algún lugar — y si ese registro se pierde (fallo a mitad de la petición, timeout, un
deploy malo), la transacción queda huérfana: dinero recibido sin pedido correspondiente.
Con externalId, la búsqueda funciona en ambos sentidos, y el mismo endpoint acepta tres claves:
GET /transactions/:id
Resuelve por ID de Pagnovo, por tu externalId o por el end2End del PIX. Eso cubre
prácticamente cualquier escenario de investigación.
Las tres fuentes que deben coincidir
Una conciliación honesta compara tres cosas:
| Fuente | Qué responde |
|---|---|
| Tu base de datos | Lo que esperaba recibir |
| API de Pagnovo | Lo que se procesó efectivamente |
| Saldo de la cuenta | Cuánto está realmente disponible |
Si solo comparas las dos primeras, no percibes valores retenidos. Por eso importa la tercera.
Saldo: no todo lo que entró está disponible
GET /accounts/balance
La respuesta separa el saldo en categorías — y esa separación es lo que evita el susto a fin de mes:
- Disponible — se puede retirar ahora
- En liquidación — ya transaccionado, aún no liquidado
- Depósitos de garantía (collateral)
- Bloqueos cautelares
- Reservas de contracargo
Un error común es sumar todo y creer que es caja. No lo es: la reserva de contracargo y el bloqueo cautelar existen pero no pueden usarse. Tu conciliación debe tratar cada categoría por separado, y tu flujo de caja debe mirar solo el disponible.
La rutina de conciliación
Un diseño que funciona bien en la práctica, ejecutándose algunas veces al día:
// 1. Pedidos que creé pero aún no confirmé
const pendientes = await db.orders.find({
status: 'PENDING',
createdAt: { $gte: ayer },
});
for (const pedido of pendientes) {
// 2. Le pregunto a la fuente de verdad, usando MI identificador
const tx = await pagnovo.get(`/transactions/${pedido.externalId}`);
// 3. Reconcilio el estado
if (tx.status === 'APPROVED' && pedido.status !== 'PAID') {
await liberarPedido(pedido, tx); // el webhook falló — lo recupero aquí
}
if (['REJECTED', 'BLOCKED'].includes(tx.status)) {
await marcarFallo(pedido, tx.status);
}
if (tx.status === 'INCONSISTENT') {
await señalarParaHumano(pedido, tx); // no decidas solo
}
}
Tres principios incorporados ahí:
- La API es la fuente de verdad, no tu base de datos.
- El job repara lo que el webhook perdió. El webhook es el camino rápido; la conciliación es la red.
INCONSISTENTno se resuelve automáticamente. Un estado ambiguo va a revisión humana — con dinero, adivinar sale caro.
Los estados y qué hacer con cada uno
| Status | Significado | Acción |
|---|---|---|
PENDING |
Esperando pago | Mantener en observación, respetar expiración |
APPROVED |
Pagado | Liberar pedido (idempotente) |
REJECTED |
Rechazado | Cerrar, ofrecer nuevo intento |
INCONSISTENT |
Divergencia | Revisión manual |
BLOCKED |
Bloqueado | Revisión + contacto con soporte |
Paginación: no pierdas registros por el camino
Al listar, la API usa paginación indexada en cero, con límite por defecto de 20 y máximo de 100.
?page=0&limit=100
El error clásico es leer solo la primera página y concluir que "hubo 20 transacciones en el período". Recorre siempre hasta la última página antes de cerrar cualquier número.
x-trace-id: lo que salva a tu soporte
Cada respuesta de la API incluye el header x-trace-id. Guárdalo junto al registro de la
transacción.
Cuando surja un caso del tipo "el cliente jura que pagó", abres un ticket con el x-trace-id y el
equipo encuentra exactamente esa petición en los logs — en vez de una investigación aproximada
usando hora e importe.
Es una línea de código en tu cliente HTTP que ahorra horas después.
Probar el flujo de conciliación
En el sandbox (sk_test_*), los importes generan resultados determinísticos — incluidos los
incómodos:
| Importe | Estado generado |
|---|---|
| R$ 10,00 | Aprobado |
| R$ 10,01 | Rechazado |
| R$ 10,02 | Inconsistente |
| R$ 10,04 | Contracargo |
| R$ 10,05 | Reembolso |
| R$ 10,06 | Bloqueado |
Es decir: puedes probar a propósito el camino del INCONSISTENT y del contracargo, que son
justamente los que rompen una conciliación mal hecha. El entorno de pruebas permite 30 operaciones
por día.
Checklist
-
externalIdenviado en todos los cobros -
x-trace-idguardado con la transacción - Job periódico recorriendo pedidos pendientes
- Liberación de pedido idempotente (webhook y job no pueden duplicar)
-
INCONSISTENTyBLOCKEDderivados a revisión humana - Paginación recorrida hasta el final
- Saldo leído por categoría, no sumado
- Alerta cuando la cola de pendientes crece fuera de lo normal
Conoce nuestra plataforma de Cobros y la documentación de la API. ¿Necesitas ayuda para diseñar tu conciliación? Habla con el equipo.