How to integrate the Pagnovo PIX API: a developer's guide

A practical guide to the Pagnovo API: authentication, creating a PIX charge, brCode, HMAC-signed webhooks, sandbox testing and the most common integration mistakes.

Pagnovo Team · 2026-08-04

This guide covers the full path of a PIX integration with Pagnovo — from your first authenticated call to the confirmation webhook — with code that actually works against our API.

Full, always-current reference: portal.pagnovo.com/docs.

First, how PIX works under the hood

PIX is the Central Bank of Brazil's instant payment system. The payer initiates the payment, their institution sends the order to the SPI, which validates, debits and credits — all in seconds, 24/7.

You do not connect to the Central Bank directly: only authorized institutions reach the SPI. Your application talks to Pagnovo, which exposes a REST API on top of it.

Step 1 — Authentication

Pagnovo uses secret key authentication through the Authorization header, in Basic format:

curl -X POST https://api.pagnovo.com/transactions/v2/purchase \
  -H "Authorization: Basic $(echo -n 'secret:sk_test_YOUR_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 18990, "description": "Order #1042", "externalId": "order-1042" }'

Two important details:

Optionally you can configure an IP allowlist for createWithdraw and createRefund. With it active, calls from another IP return 400 "IP unauthorized".

Step 2 — Amounts in cents

This is the number one beginner mistake: all monetary values are integers in cents.

Real amount amount field
R$ 1.00 100
R$ 189.90 18990
R$ 1,500.00 150000

Percentage coupons and interest use basis points (1,000 = 10%); fixed penalties use cents.

Step 3 — Create the charge

POST /transactions/v2/purchase

{
  "amount": 18990,
  "description": "Order #1042",
  "externalId": "order-1042",
  "postbackUrl": "https://your-app.com/webhooks/pagnovo",
  "restrictPayerDocument": true
}

Fields worth attention:

The response returns:

{
  "id": "...",
  "status": "PENDING",
  "amount": 18990,
  "brCode": "00020126...",
  "qrCode": "..."
}

The brCode is the EMV copy-and-paste string; qrCode is the representation to display.

Step 4 — Show it to the customer

Offer both formats:

Forcing only the QR on mobile users is one of the biggest causes of abandonment.

Step 5 — Webhooks

Register the endpoint once and pick your events:

POST /v2/webhooks

{
  "url": "https://your-app.com/webhooks/pagnovo",
  "description": "Production",
  "events": ["cashin.paid", "cashin.refunded", "cashout.success"]
}

The response returns a plain-text secret exactly once — store it safely. If you lose it, use POST /v2/webhooks/:id/rotate-secret.

Available events include cashin.paid, cashin.refunded, cashout.success, cashout.failed, cashout.returned, infraction.updated (replacing the old CHARGEBACK/BLOCKED) and the subscription lifecycle (subscription.created, .activated, .paused, .canceled, .past_due, .expired).

The V2 envelope is standardized:

{
  "event": "cashin.paid",
  "environment": "TEST",
  "payload": { "id": "...", "status": "APPROVED", "amount": 18990 }
}

Step 6 — Verify the signature (HMAC)

Never process a webhook without verifying the signature. An open endpoint is an invitation for someone to mark orders as paid without paying.

Pagnovo signs with HMAC-SHA256 over the payload with sorted keys:

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

// Compare in constant time
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));

The three classic causes of an invalid signature:

  1. Different re-serialization — whitespace or changed key order
  2. Unsafe comparison — using === instead of a constant-time comparison
  3. Wrong secret — mixing up the test and production keys

Step 7 — Reconcile

Webhooks are the primary path, but your application may be down when the call happens. Keep a fallback lookup for pending orders:

GET /transactions/:id

This endpoint accepts Pagnovo's ID, your externalId, or the PIX end2End — which is why it is always worth sending externalId at creation.

Every response carries the x-trace-id header; keep it in your logs. That is what support uses to find the exact request on our servers.

Step 8 — Test in the sandbox

Use an sk_test_* key. The sandbox has deterministic outcomes by amount, letting you exercise every path without relying on luck:

Amount Outcome
R$ 10.00 Approved
R$ 10.01 Rejected
R$ 10.02 Inconsistent
R$ 10.04 Chargeback
R$ 10.05 Refund
R$ 10.06 Blocked

The test environment is capped at 30 operations per day (transactions + withdrawals + refunds).

Most common mistakes

Pre-production checklist


Start with the official documentation, create your account at portal.pagnovo.com and explore our PIX API. Questions about your integration? Talk to our team.