Skip to content

Updated

GiroCode on invoices: the standard and the pitfalls

What an EPC QR code (GiroCode) contains, field by field after EPC069-12, which limits apply — and why so many generated codes fail on a check digit, a comma or an umlaut.

The square at the bottom of a European invoice that a banking app scans to fill in a transfer is called a GiroCode in Germany and an EPC QR code elsewhere. It is not a bank’s product but an open standard of the European Payments Council, described in EPC069-12, Quick Response Code: Guidelines to Enable Data Capture for the Initiation of a SEPA Credit Transfer. It is free to use and short enough to read in an afternoon.

We read it twice this week: once for a GiroCode generator that runs entirely in the browser, and once more when the checker we wrote for it rejected our own sample invoices. That is part of this post too.

Up to twelve lines of text

A GiroCode holds no link and no identifier, only up to twelve lines of plain text separated by line breaks. The app reads them and fills its transfer form; the payer still confirms it there.

Line Content Limit per EPC069-12
1 Service tag BCD fixed
2 Version 001 or 002 from 002 the BIC is optional within the EEA
3 Character set, 1 to 8 1 means UTF-8
4 Identification SCT (SEPA Credit Transfer) fixed
5 BIC 8 or 11 characters
6 Beneficiary name at most 70 characters
7 IBAN at most 34 characters, IBAN only
8 Amount, EUR followed by the number EUR0.01 to EUR999999999.99, optional
9 Purpose code (AT-T007) at most 4 characters, optional
10 Structured reference at most 35 characters, optional
11 Unstructured remittance text at most 140 characters, optional
12 Note to the payer at most 70 characters, optional

Three rules reach beyond single lines: lines 10 and 11 are mutually exclusive, no line break follows the last populated line, and the whole content must not exceed 331 bytes.

Where generated codes fail

None of the following mistakes shows when the code is made. Most libraries encode whatever they are given, and the result is a flawless QR code whose content an app that follows the standard cannot take over. The payer is the first to notice — after scanning the invoice.

The IBAN check digits

The two digits after the country code of an IBAN are a checksum over the rest, computed with ISO 7064 Mod 97-10: move the first four characters to the end, turn letters into numbers (A = 10 to Z = 35), and the whole number modulo 97 must leave 1.

Sample data usually takes an IBAN of zeros, DE00 0000 0000 0000 0000 00. It cannot exist: 00 is never valid check digits. For a German account number of zeros the right ones are 36, so DE36 0000 0000 0000 0000 00 — still nobody’s account, but valid.

We learned this the unpleasant way: our own sample invoice and sample payment reminder had carried DE00… in their GiroCode for weeks. Only the checker we wrote for the generator rejected them. Both are fixed now.

Building the check yourself has one more trap: the rearranged number has between 20 and over 30 digits depending on the country, 24 for a German IBAN, far beyond the roughly 16 digits a floating-point number holds exactly. % 97 on the whole number silently gives a wrong result; the remainder is computed digit by digit.

const rearranged = iban.slice(4) + iban.slice(0, 4);
const digits = [...rearranged]
  .map((c) => (c >= 'A' && c <= 'Z' ? c.charCodeAt(0) - 55 : c))
  .join('');
let rest = 0;
for (const d of digits) rest = (rest * 10 + Number(d)) % 97;
return rest === 1;

The comma in the amount

Line 8 requires a dot as the decimal separator: EUR1234.50. Formatting the amount for a German, French or Dutch reader writes EUR1234,50 — readable for people, but not an amount in the sense of the standard. The mistake rarely sits in the GiroCode code itself, but one layer above, where someone formats the amount for display and passes the same string on.

Structured or free text

Line 10 is the structured creditor reference; the standard names the RF reference of ISO 11649 for it, which starts with RF and two check digits. An invoice number like 2026-0042 is no such reference, and a banking app that treats the field as structured cannot use it. The invoice number belongs in line 11 as remittance text. Filling both lines is not allowed, and which one a library then drops is up to the library, not the standard.

Our samples had this wrong as well: they carried the invoice number in line 10, although the payment reminder called it the “Verwendungszweck”, the remittance text, in its own caption.

331 bytes, not characters

The limit is in bytes, and in UTF-8 an umlaut costs two of them. A remittance text of 140 characters that respects its field limit can break the total limit with enough ä, ö and ü.

The number itself is no accident: the standard fixes error correction level M and allows QR version 13 at most, 69 × 69 modules. Exactly 331 bytes fit into such a code.

The declared and the actual character set

Line 3 declares how the rest is encoded. If a code declares 1 (UTF-8) but writes its text in ISO 8859-1, umlauts in the beneficiary name come out as garbage. The code is then not invalid, just wrong — which is worse, because nothing checks it.

In the template

In a Formfeed template the GiroCode is built from the invoice’s data with the epcQr helper. This is how it appears in our sample invoice, as the render server produces it:

<img src="{{ epcQr({
  name: company.name,
  iban: company.iban,
  bic: company.bic,
  amount: net + vat,
  text: 'Rechnung ' ~ invoice.number
}, { size: 110 }) }}" alt="GiroCode">

The amount comes from the same invoice as the total above it, the invoice number goes into line 11 as remittance text. The helper reference shows how Liquid and Handlebars write the call. The fields are the same in all three: text fills line 11, and reference belongs to an RF creditor reference only.

The sample invoice as the render server produces it, with the GiroCode at the bottom left

What the helper does and does not do: epcQr builds the lines after the standard and draws the code with error correction M. It does not check the data while rendering; the generator does, building the same lines from the same code and checking the check digits, the lengths, the amount range and the 331 bytes first. Paste your master data there once and you know whether the code on your next invoice is right.

What a GiroCode is not

It is a credit transfer and nothing else: no direct debit, no second currency, no payment request that triggers anything without the payer. And it is not an e-invoice. ZUGFeRD, Factur-X and XRechnung make the invoice itself machine-readable for the recipient’s accounting; the GiroCode helps a person pay. Both fit on the same invoice.

The standard and the field details were checked on 21 September 2026 against EPC069-12 version 3.1 (19 March 2024).

Philipp Staudt

Start rendering today

The free plan includes 100 units a month and free test renders. Change plans when you need more.