Cómo integrar la API PIX de Pagnovo: guía para desarrolladores
Guía práctica de la API Pagnovo: autenticación, creación de cobro PIX, brCode, webhooks con HMAC, sandbox y los errores más comunes al integrar.
Equipo Pagnovo · 2026-08-04
Esta guía cubre el camino completo de una integración PIX con Pagnovo — de la primera llamada autenticada al webhook de confirmación — con el código que realmente funciona en nuestra API.
Referencia completa y siempre actualizada: portal.pagnovo.com/docs.
Antes de empezar: cómo funciona PIX por dentro
PIX es el sistema de pagos instantáneos del Banco Central de Brasil. El pagador inicia el pago, su institución envía la orden al SPI, que valida, debita y acredita — todo en segundos, 24/7.
No te conectas al Banco Central directamente: solo las instituciones autorizadas acceden al SPI. Tu aplicación habla con Pagnovo, que expone una API REST por encima.
Paso 1 — Autenticación
Pagnovo usa autenticación por clave secreta mediante el header Authorization, en formato Basic:
curl -X POST https://api.pagnovo.com/transactions/v2/purchase \
-H "Authorization: Basic $(echo -n 'secret:sk_test_TU_CLAVE' | base64)" \
-H "Content-Type: application/json" \
-d '{ "amount": 18990, "description": "Pedido #1042", "externalId": "order-1042" }'
Dos detalles importantes:
- El entorno viene de la clave, no de la URL.
sk_test_*opera en pruebas;sk_live_*en producción (tras la aprobación del KYC). La base siempre eshttps://api.pagnovo.com. - Cada clave lleva permisos (scopes). La lectura exige
view<Recurso>; la escritura,manage<Recurso>. Sin el permiso obtienes 403 "Unauthorized Permissions" — distinto de 401, que significa credencial inválida o ausente.
Opcionalmente puedes configurar una lista blanca de IP para createWithdraw y createRefund.
Con ella activa, las llamadas desde otra IP devuelven 400 "IP unauthorized".
Paso 2 — Importes en centavos
Este es el error número uno al empezar: todos los valores monetarios son enteros en centavos.
| Importe real | Campo amount |
|---|---|
| R$ 1,00 | 100 |
| R$ 189,90 | 18990 |
| R$ 1.500,00 | 150000 |
Los cupones porcentuales e intereses usan basis points (1.000 = 10%); las multas fijas, centavos.
Paso 3 — Crear el cobro
POST /transactions/v2/purchase
{
"amount": 18990,
"description": "Pedido #1042",
"externalId": "order-1042",
"postbackUrl": "https://tu-app.com/webhooks/pagnovo",
"restrictPayerDocument": true
}
Campos que merecen atención:
externalId— el identificador del pedido en tu sistema. Envíalo siempre: permite buscar la transacción después sin guardar el ID de Pagnovo.restrictPayerDocument— cuando estrue, solo el CPF/CNPJ indicado puede pagar ese QR. Excelente contra el pago de terceros y útil en antifraude.postbackUrl— webhook por transacción (modelo V1). Para producción, prefiere los webhooks V2 registrados una vez (paso 5).
La respuesta trae:
{
"id": "...",
"status": "PENDING",
"amount": 18990,
"brCode": "00020126...",
"qrCode": "..."
}
El brCode es la cadena EMV del copiar-y-pegar; el qrCode, la representación para mostrar.
Paso 4 — Mostrarlo al cliente
Ofrece las dos formas:
- Copiar-y-pegar con botón de copiar — esencial en móvil, donde el usuario está en el mismo aparato y no puede escanear su propia pantalla
- Código QR generado a partir del
brCode
Forzar solo el QR a quien está en el celular es una de las mayores causas de abandono.
Paso 5 — Webhooks
Registra el endpoint una vez y elige los eventos:
POST /v2/webhooks
{
"url": "https://tu-app.com/webhooks/pagnovo",
"description": "Producción",
"events": ["cashin.paid", "cashin.refunded", "cashout.success"]
}
La respuesta devuelve un secret en texto plano una sola vez — guárdalo con seguridad. Si lo
pierdes, usa POST /v2/webhooks/:id/rotate-secret.
Los eventos disponibles incluyen cashin.paid, cashin.refunded, cashout.success,
cashout.failed, cashout.returned, infraction.updated (reemplaza los antiguos CHARGEBACK/BLOCKED)
y el ciclo de suscripciones (subscription.created, .activated, .paused, .canceled,
.past_due, .expired).
El envelope V2 está estandarizado:
{
"event": "cashin.paid",
"environment": "TEST",
"payload": { "id": "...", "status": "APPROVED", "amount": 18990 }
}
Paso 6 — Validar la firma (HMAC)
Nunca proceses un webhook sin validar la firma. Un endpoint abierto es una invitación a que alguien marque pedidos como pagados sin pagar.
Pagnovo firma con HMAC-SHA256 sobre el payload con las claves ordenadas:
import crypto from 'crypto';
function sortObjectKeys(obj: any): any {
if (Array.isArray(obj)) return obj.map(sortObjectKeys);
if (obj !== null && typeof obj === 'object') {
return Object.keys(obj).sort().reduce((acc, key) => {
acc[key] = sortObjectKeys(obj[key]);
return acc;
}, {} as any);
}
return obj;
}
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(sortObjectKeys(payload)))
.digest('hex');
// Compara en tiempo constante
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(recibida));
Las tres causas clásicas de firma inválida:
- Reserialización diferente — espacios en blanco u orden de claves alterado
- Comparación insegura — usar
===en vez de comparación en tiempo constante - Secret equivocado — confundir la clave de prueba con la de producción
Paso 7 — Conciliar
El webhook es el camino principal, pero tu aplicación puede estar caída en el momento de la llamada. Mantén una consulta de respaldo para pedidos pendientes:
GET /transactions/:id
Este endpoint acepta el ID de Pagnovo, tu externalId o el end2End del PIX — por eso
siempre vale enviar externalId en la creación.
Cada respuesta trae el header x-trace-id; guárdalo en tus logs. Es lo que el soporte usa para
encontrar la petición exacta en nuestros servidores.
Paso 8 — Probar en el sandbox
Usa una clave sk_test_*. El sandbox tiene resultados determinísticos por importe, lo que
permite probar todos los caminos sin depender de la suerte:
| Importe | Resultado |
|---|---|
| R$ 10,00 | Aprobado |
| R$ 10,01 | Rechazado |
| R$ 10,02 | Inconsistente |
| R$ 10,04 | Contracargo |
| R$ 10,05 | Reembolso |
| R$ 10,06 | Bloqueado |
El entorno de pruebas tiene un límite de 30 operaciones por día (transacciones + retiros + reembolsos).
Errores más comunes
- Enviar el importe en reales.
189.90se convierte en R$ 1,89. Siempre centavos. - Confirmar el pedido al crearlo. Cobro creado ≠ pagado. El
statusnace comoPENDING. - No validar el HMAC. Exposición directa a fraude.
- Ignorar la idempotencia. El mismo webhook puede llegar más de una vez — usa el
idde la transacción como clave y responde 200 si ya lo procesaste. - Responder lento. Devuelve 200 rápido y procesa en cola; la lentitud se trata como fallo y genera reenvío.
- Confundir 401 con 403. 401 = credencial inválida. 403 = clave válida, sin el permiso necesario.
- No guardar
externalId. Sin él, conciliar después se vuelve mucho más difícil.
Checklist antes de producción
- Clave
sk_live_*con los permisos mínimos necesarios - Importes siempre en centavos (prueba con
18990, no189.90) - Webhook V2 registrado, con el secret almacenado de forma segura
- Validación HMAC con comparación en tiempo constante
- Idempotencia por
idde la transacción - Respuesta 200 inmediata + procesamiento asíncrono
- Respaldo con
GET /transactions/:id -
x-trace-idregistrado en los logs - Flujos de rechazo y reembolso probados en el sandbox
Empieza por la documentación oficial, crea tu cuenta en portal.pagnovo.com y conoce nuestra API PIX. ¿Dudas en la integración? Habla con el equipo.