Croatia Fiscalization Guide
What is Croatian fiscalization?
Croatia operates Fiskalizacija, run by Porezna uprava (PU — Croatian Tax Administration) through the CIS (Centralni Informacijski Sustav — Central Information System). Cash invoices must be confirmed by CIS at the moment of issuance; CIS responds with a unique invoice identifier (JIR) which must appear on the receipt alongside the locally-computed protective code (ZKI).
In plain English: Like Slovenia, Croatia is a synchronous "approve-each-sale" regime. The cashier doesn't notice — Zyntem completes the round-trip in well under a second.
Zyntem manages the FINA-issued application certificate, computes the ZKI, signs the SOAP envelope with XMLDSIG, talks to CIS, parses the response, and prints both the ZKI and the JIR on your receipt.
How it works
Croatia's flow is synchronous and online, with a 48-hour grace period if CIS is unreachable:
- Your POS creates a transaction via
POST /v1/transactions. - Zyntem computes the ZKI (Zaštitni kod izdavatelja — Issuer Protection Code), an MD5 hash of taxpayer + receipt-number + timestamp + total, signed with the FINA cert.
- Zyntem submits the SOAP
RacunZahtjevrequest to CIS over TLS (cis.porezna-uprava.hr:8449). - CIS responds with the JIR (Jedinstveni identifikator računa — Unique Invoice Identifier).
- The transaction returns with
fiscal_id= ZKI andfiscal_id_upstream= JIR.
Submission window: Synchronous. If CIS is unreachable, Croatia allows 48 hours to retry — Zyntem queues with the ZKI on the receipt and the JIR is filled in once CIS accepts.
Fiskalizacija 2.0: Set use_v2: true to enable the new namespace (f95), RSA-SHA256 ZastKodv2, and tip support. Default false keeps v1.x semantics. Coordinate with PU before flipping for a live merchant.
Country config fields
The country_config object for Croatian locations:
| Field | Type | Required | Description |
|---|---|---|---|
oib | string | Yes | 11-digit Croatian taxpayer ID (no HR prefix). |
signing_key_id | string | Yes | KMS reference for the FINA-issued PFX. |
poslovni_prostor | string (≤20) | Yes | OznPosPr — pre-registered business premise mark. |
naplatni_uredjaj | string (≤20) | Yes | OznNapUr — cash register / device mark within the premise. |
ozn_slijed | string | Yes | P = numbering scoped to premise, N = scoped to (premise, device). |
u_sust_pdv | boolean | Yes | True if the merchant is registered in the VAT system (USustPdv). |
operator_oib | string | Yes | Default cashier OIB written to OibOper. |
default_nacin_plac | string | Yes | Default payment method: G cash, K card, C cheque, T transfer, O other. |
use_v2 | boolean | No | Enable Fiskalizacija 2.0 (default false). |
Premise pre-registration. Each new
poslovni_prostormust be pre-registered with PU via aPoslovniProstorrequest before any invoices can be issued from it. Zyntem performs this registration automatically when you create the location.
Configuration example
curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Zagreb Store",
"country": "HR",
"address": "Ilica 1, 10000 Zagreb",
"country_config": {
"oib": "11111111119",
"signing_key_id": "kms://hr/fina-cert",
"poslovni_prostor": "POSL1",
"naplatni_uredjaj": "1",
"ozn_slijed": "N",
"u_sust_pdv": true,
"operator_oib": "11111111119",
"default_nacin_plac": "G"
}
}'
Creating a transaction
Example: A EUR 24.00 sale at a Zagreb store, paid by card.
curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"amount": 2400,
"currency": "EUR",
"payment_method": "card",
"line_items": [
{
"description": "T-shirt",
"quantity": 1,
"unit_price": 2400,
"vat_rate": 2500
}
]
}'
Response
{
"id": "txn_abc123",
"status": "success",
"fiscal_id": "a3b2c1...32hex",
"fiscal_id_upstream": "12345678-90AB-CDEF-1234-567890ABCDEF",
"country": "HR",
"created_at": "2026-03-15T14:30:22Z"
}
Both ZKI (fiscal_id) and JIR (fiscal_id_upstream) are required on the printed receipt under Croatian law.
Receipt numbering
Croatia uses a composite receipt number: <sequence>/<premise>/<device> (e.g. 42/POSL1/1). The counter restarts at 1 each calendar year. Zyntem manages the counter automatically; you never assign receipt numbers.
Sandbox access
CIS demo runs at cistest.apis-it.hr:8449. Sandbox routing is automatic with a test API key. Sandbox FINA certificates are obtained from demo.e-porezna.hr — typical onboarding is 1–2 weeks.
Offline-grace behaviour
CIS returns the JIR synchronously, so the receipt-print path performs the authority round-trip while the customer waits. Zyntem applies a 3-second timeout per attempt. If CIS is unreachable or the response times out, the receipt still prints — Croatian law mandates a graceful-degradation ("ne dostavljen") flow:
- The receipt carries the locally-computed ZKI and is annotated with the regulator-mandated offline label so the customer's copy is unambiguous.
- The transaction is recorded as
offline_pendingand Zyntem retries the CIS submission in the background through the 48-hour offline-grace window defined by HRZakonOFiskalizaciji. - On a successful retry, the JIR is populated (
fiscal_id_upstream) and atransaction.late_authority_confirmedwebhook fires. Print a corrected copy if the merchant retains them. - If the 48-hour window expires without success, the row transitions to
failed_offline_grace_expiredand a webhook fires for human triage.
There are no merchant-facing knobs for this — the timeout, label, and grace window are fixed by regulation. ISVs do not need to implement fallback logic in their application; Zyntem owns the full state machine.
Error handling
When CIS rejects an invoice, error.category distinguishes:
transient— network blip or CIS 5xx. Zyntem retries within the 48-hour window.regulatory_reject— CIS business-validation error (error.authority_codecarries thes00xSOAP fault code).duplicate_as_success— CIS responded with a JIR collision (re-submission of an already-accepted invoice). Treated as success.
See the Error handling guide for the full taxonomy.
Receipt presentation
render_hints on the transaction response tells your POS exactly what to
print and where. You do not need to read the Zakon o fiskalizaciji —
the hints are the contract.
For Croatia, the items returned are:
key | Always present? | Placement | Mandatory | Kind |
|---|---|---|---|---|
zki | Yes | footer_bottom | Yes (regulation §27) | text |
qr | Yes | footer_right | Recommended | qr_url (min 25 mm) |
jir | Only on online success | footer_bottom | Yes when present | text |
subsequent_delivery_label | Only on offline-grace fallback | footer_top | Yes | text ("naknadna dostava") |
The runtime appends jir after the synchronous CIS round-trip succeeds; in
offline-grace mode it appends subsequent_delivery_label instead. The
zki and qr items are always populated.
Example printed receipt (online — JIR present)
═══════════════════════════════
POSLOVNICA POSL1 / 1
═══════════════════════════════
Espresso 12,50 EUR
───────────────────────────────
UKUPNO 12,50 EUR
═══════════════════════════════
ZKI: 5d6f3b8c0a1e2d4f8a7b9c1e0d2a4b6c
JIR: f01ec7d3-d2b2-4c23-aff8-4d3aa3b3b4f7
┌────────┐
│ ▓░▓░░ │
│ ░▓░▓▓ │
│ ▓▓░░▓ │
└────────┘
Example printed receipt (offline-grace — naknadna dostava)
═══════════════════════════════
NAKNADNA DOSTAVA
POSLOVNICA POSL1 / 1
═══════════════════════════════
Espresso 12,50 EUR
───────────────────────────────
UKUPNO 12,50 EUR
═══════════════════════════════
ZKI: 5d6f3b8c0a1e2d4f8a7b9c1e0d2a4b6c
(JIR added on retransmission)
┌────────┐
│ ▓░▓░░ │
└────────┘
meta schema (Croatia)
| Key | Item | Description |
|---|---|---|
min_size_mm | qr | Recommended minimum module size for the QR code. CIS does not strictly mandate a minimum, but 25 mm is the operational baseline that scans reliably. |