Skip to content

QR codes

Every fiscalized receipt carries a QR code. FiskHub returns the exact string to encode as qr_payload; rendering and printing it correctly is the device’s job, and the regulation is specific about how.

Encode qr_payload verbatim

{
"qr_payload": "https://porezna.gov.hr/rn?jir=2cf55235-9470-4b5c-a539-463f52b109d2&datv=20260827_1032&izn=450"
}

Pass that string to your QR encoder unchanged. Do not re-order the parameters, do not percent-encode them again, do not append your own, do not shorten the URL. The payload is constructed to the specification and any edit risks producing a code the Tax Administration’s verification app cannot read.

The two forms

The payload takes one of two shapes depending on whether the receipt has been confirmed.

https://porezna.gov.hr/rn?jir=<JIR>&datv=<YYYYMMDD_HHMM>&izn=<amount>
https://porezna.gov.hr/rn?zki=<ZKI>&datv=<YYYYMMDD_HHMM>&izn=<amount>
ParameterMeaningFormat
jirThe Tax Administration’s receipt identifierUUID. Present when the receipt was confirmed.
zkiThe issuer protection code32 lowercase hex characters. Used when there is no JIR.
datvThe sale date and time, Europe/ZagrebYYYYMMDD_HHMM
iznThe totalEuros and cents, no separator, no leading zeros

izn is the whole amount in cents: €4.50 becomes 450, €1,525.99 becomes 152599, and a negative amount keeps its sign — -10550. There is no decimal point and no thousands separator.

Which form you get

Invoice statusqr_payload carries
confirmedjir=
retryingzki=
failedzki=
expiredzki=

You never choose. Print whichever string the response contains.

The payload returned by a POST reflects that moment. If an invoice returned as retrying is confirmed later by the retry sweep, reading it back with GET /v1/invoices/{id} returns the jir= form — but the receipt already in the customer’s hand remains valid and does not need reprinting.

Printing requirements

These come from the specification (§2.7) and are not suggestions. A QR code that fails any of them may be unreadable by the verification app, which is what the customer will actually try.

RequirementValue
QR modelModel 1 or Model 2
VersionThe smallest version that fits the payload
Printed sizeAt least 2 × 2 cm
Quiet zoneAt least 2 mm of clear margin on all four sides
Error correctionLevel L or higher
Print qualityISO/IEC 15415
OverlayNo logo, no image, no text over the code

Practical notes for thermal printers

  • At 203 dpi, 2 cm is about 160 dots. Round the module size up rather than down; a code slightly larger than the minimum is fine, one slightly smaller is not.
  • Print the code at native resolution. Scaling a bitmap QR blurs module edges and destroys contrast.
  • Contrast matters as much as size. Faded thermal output, a low-quality roll, or a printer due a head clean will produce a code that is geometrically perfect and optically unreadable.
  • Error correction level M is a good default if your encoder makes you pick. It exceeds the minimum and costs a little size, which is cheap insurance on receipt paper.

Rendering it

FiskHub returns the payload string, not an image. Any standard encoder will do.

import QRCode from "qrcode";
// 2 cm at 203 dpi is about 160 dots. `width` is in pixels; `margin` is in
// MODULES, and 4 modules is the standard quiet zone — comfortably over the
// 2 mm minimum at this size.
const png = await QRCode.toBuffer(invoice.qr_payload, {
errorCorrectionLevel: "M",
width: 160,
margin: 4,
color: { dark: "#000000ff", light: "#ffffffff" },
});
await printer.image(png);
import qrcode
qr = qrcode.QRCode(
version=None, # smallest that fits
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=6, # modules -> dots
border=4, # quiet zone, in modules
)
qr.add_data(invoice["qr_payload"])
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")

What else goes on the receipt

The QR code does not replace the printed fields.

PrintedSourceWhen
Receipt numbernumberAlways
ZKIzkiAlways
JIRjirOnly when non-null — omit the line entirely otherwise
QR codeqr_payloadAlways
Date and timeoccurred_atAlways
TotaltotalAlways

Never print JIR: null, JIR: -, or an empty JIR label. When there is no JIR, the line does not appear; the ZKI and the QR code carry the receipt.

Verifying your output

Scan a printed receipt with a phone. The payload opens the Tax Administration’s receipt verification page. A jir= code resolves to the confirmed receipt; a zki= code is the protection-code form, which is what a customer holding an unconfirmed receipt is meant to have.

Test with codes printed on the real printer, on the real paper, at the real size. Screen renders hide every failure mode that matters.