Use case

Adding PIX QR codes to your product

The decision that matters is static versus dynamic, and it is a reconciliation decision, not a technical one. Get it wrong and you spend a quarter matching payments by hand.

Updated 2026-08-07 · 8 min read · by the FaturaPDF team

PIX is how Brazil pays. Adding it to an invoice or a checkout is mostly a matter of putting the right string in front of the payer — but there is one architectural decision underneath, and teams routinely discover it after launch, when someone asks "so how do we know which invoice this payment was for?"

Static or dynamic: decide this first

Static BR CodeDynamic BR Code
What it encodesYour PIX key, directly, plus optional amountA URL to a payload hosted by your PSP
Who can create itYou, offline, in ~40 linesYour bank or PSP, via their API
ReusableYes, indefinitelyTypically per-charge, with an expiry
Per-charge amountPossible, but the code is still fungibleYes, bound to the charge
Automatic reconciliationNo — see belowYes — webhook identifies the charge
CostZeroPSP pricing, account, integration
Good forInvoices a human reconciles, donations, tip jars, small volumeCheckout, subscriptions, anything automated
The reconciliation trap

A static code with an amount and a txid looks like it supports reconciliation. It does not, reliably: the payer's bank does not guarantee your reference reaches your statement in a machine-readable form, the payer can edit the amount in many flows, and nothing stops the same code being paid twice. If your process is "customer pays, system marks invoice paid, automatically", you need dynamic codes and a PSP webhook. If your process is "the finance team looks at the statement", static is fine and free.

Practical middle ground used by a lot of small Brazilian businesses: static code on the invoice, with the invoice number in the description field, and a human matching the bank statement. It works, it costs nothing, and it stops working somewhere around a few dozen payments a month.

Generating a static code

The payload is EMV type-length-value ASCII — every field is documented in the BR Code format reference, with a real payload dissected byte by byte. The generator:

javascriptpix.js
const tlv = (id, v) => id + String(String(v).length).padStart(2, "0") + v;

/** Uppercase, unaccented, [A-Z0-9 ] only. 1 char must equal 1 byte. */
const clean = (s, max) =>
  String(s ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "")
    .toUpperCase().replace(/[^A-Z0-9 ]/g, "").trim().replace(/\s+/g, " ").slice(0, max);

function crc16(str) {
  let crc = 0xffff;
  for (let i = 0; i < str.length; i++) {
    crc ^= str.charCodeAt(i) << 8;
    for (let b = 0; b < 8; b++) {
      crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;
    }
  }
  return crc.toString(16).toUpperCase().padStart(4, "0");
}

export function buildPixCode({ key, name, city, amount, txid }) {
  const account = tlv("00", "BR.GOV.BCB.PIX") + tlv("01", key);
  const body =
    tlv("00", "01") + tlv("01", "11") + tlv("26", account) +
    tlv("52", "0000") + tlv("53", "986") +
    (amount > 0 ? tlv("54", Number(amount).toFixed(2)) : "") +
    tlv("58", "BR") + tlv("59", clean(name, 25)) + tlv("60", clean(city, 15)) +
    tlv("62", tlv("05", clean(txid, 25) || "***")) + "6304";
  return body + crc16(body);
}

Render it with any QR library at error-correction level M. Or skip the code entirely while prototyping: the free PIX tool builds and displays one in your browser and shows the resulting string, which is a fast way to sanity-check your own implementation against a known-good output.

Putting it on the invoice

The pattern that works: total, amount in words, QR code, and the copy-paste string underneath — because a meaningful share of payers are on the same device and cannot scan their own screen.

javascriptinvoice-with-pix.js
const brCode = buildPixCode({
  key: "contato@exemplo.com.br",
  name: "Atlas Solucoes Digitais",
  city: "Sao Paulo",
  amount: 149.90,
  txid: "PED0001",
});

const res = await fetch("https://brazilian-invoice-receipt-pdf-api-cpf-cnpj.p.rapidapi.com/invoice", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
    "X-RapidAPI-Host": "brazilian-invoice-receipt-pdf-api-cpf-cnpj.p.rapidapi.com",
  },
  body: JSON.stringify({
    numero: "PED0001",
    emitente: { nome: "Atlas Solucoes Digitais LTDA", documento: "11222333000181" },
    destinatario: { nome: "Comercio Silva & Filhos ME", documento: "22333444000181" },
    itens: [{ descricao: "Licenca mensal", valor_unitario: 149.90 }],
    forma_pagamento: "PIX",
    pix_copia_cola: brCode,          // rendered as a QR on the PDF
  }),
});
Print quality is a real constraint

A 145-character payload produces a fairly dense QR. Keep it at least 3 cm square at 300 dpi with a clear quiet zone, and resist the urge to put a logo in the middle unless you have raised the error correction level to compensate. The most common "the QR does not scan" report is a printing problem, not a payload problem.

Scope: encoding is not authorization

What building a BR Code does and does not do

It formats a string. It does not contact a bank, does not verify that the key belongs to you, does not create a charge, and does not move money. The payment happens when someone scans it and their bank sends funds to whoever owns that key.

Two consequences worth designing for: (1) a typo in the key sends money to a stranger with no recall mechanism — always echo the key back for confirmation before persisting it, and never auto-populate it from an unverified source; (2) because you are asserting who should be paid, never let one user supply the key that appears on another user's document.

This is also why FaturaPDF takes pix_copia_cola as an input rather than offering to generate it: minting a PIX code implies a claim about who owns a key, and a third-party API has no basis for making that claim on your behalf. Rendering a code you supply carries no such implication.

Reconciliation, if you are doing it manually

Static codes plus human matching is a legitimate stage of a business. Make it survivable:

  • Put a short, unique reference in the txid field (62.05) — the invoice number, uppercase alphanumeric, under 25 characters. Some flows surface it; when they do, it saves minutes.
  • Also put it in the description (26.02), which some apps show the payer as they confirm. Payers who type it into the transfer note make your life much easier.
  • Set the amount when you know it. Matching by amount plus date is what actually works in practice, and distinct amounts (R$ 149,90 rather than R$ 150,00) collide far less often.
  • Track expected payments in your own system so the match is a query rather than a search.

When this stops scaling — and it will — the migration is to a PSP with dynamic codes and a webhook. Design for it now by keeping the BR Code string in a column rather than generating it inline at render time: swapping "build it locally" for "fetch it from the PSP" then touches one function.

Common mistakes

MistakeConsequence
Accented characters in name or cityByte length ≠ declared length; the payload misaligns and stricter apps reject it outright
Amount as "149,90"Rejected. Field 54 is dot-decimal, no separator, no symbol
CRC over the wrong spanUniversally rejected. It must include the trailing "6304"
Reusing one static code for every customerReconciliation becomes guesswork the moment two people owe similar amounts
Assuming txid comes back on your statementIt often does not. Do not build automation on it
Storing the QR image instead of the stringYou cannot re-render at another size, verify the CRC, or diff it

Frequently asked questions

Can I generate a PIX QR code without a bank integration?

Yes, for static codes: the payload only encodes your own PIX key, so it is pure string formatting — around 40 lines, offline, free. Dynamic codes host their payload at your PSP and do require integration.

Does the API generate PIX codes for me?

No, deliberately. FaturaPDF renders a BR Code you supply in pix_copia_cola onto the PDF. Minting a code implies asserting who owns a PIX key, which is not something a third-party API should do on your behalf — and you can build a static code yourself with the snippet above.

How do I know which invoice a PIX payment was for?

Reliably, only with a dynamic code and a PSP webhook that identifies the charge. With static codes, teams match on amount plus date plus an expected-payments list, and put the invoice number in the txid and description as a hint. That works at low volume and stops working as you grow.

What is the maximum length of a PIX payload?

There is no hard container limit, but keep it short — every character adds QR modules and hurts scannability in print. Around 150 characters is typical; the description field is the usual source of bloat.

Putting the QR on the document

FaturaPDF takes a BR Code string you already have and renders it as a scannable QR next to the total on the invoice or receipt. It never mints PIX codes — see the scope note below for why that is deliberate.

Get an API key on RapidAPI → Or try the free browser generator

Related guides