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>| Parameter | Meaning | Format |
|---|---|---|
jir | The Tax Administration’s receipt identifier | UUID. Present when the receipt was confirmed. |
zki | The issuer protection code | 32 lowercase hex characters. Used when there is no JIR. |
datv | The sale date and time, Europe/Zagreb | YYYYMMDD_HHMM |
izn | The total | Euros 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 status | qr_payload carries |
|---|---|
confirmed | jir= |
retrying | zki= |
failed | zki= |
expired | zki= |
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.
| Requirement | Value |
|---|---|
| QR model | Model 1 or Model 2 |
| Version | The smallest version that fits the payload |
| Printed size | At least 2 × 2 cm |
| Quiet zone | At least 2 mm of clear margin on all four sides |
| Error correction | Level L or higher |
| Print quality | ISO/IEC 15415 |
| Overlay | No 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.
| Printed | Source | When |
|---|---|---|
| Receipt number | number | Always |
| ZKI | zki | Always |
| JIR | jir | Only when non-null — omit the line entirely otherwise |
| QR code | qr_payload | Always |
| Date and time | occurred_at | Always |
| Total | total | Always |
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.