A Croatian customer enters an 11-digit OIB at your SaaS checkout, and your billing system rejects it as an invalid VAT number. The customer is legitimate, but the form expects the HR-prefixed VAT ID, not the domestic identifier alone. That small difference can block reverse-charge invoicing, create avoidable support tickets, and leave a valid B2B buyer unable to complete payment.
The practical rule is simple: treat Croatia's OIB and VAT ID as related identifiers with different operational uses. Normalize the input locally, validate the structure before calling an external service, and design your billing flow so a VIES outage doesn't become a checkout outage.
Table of Contents
- Understanding the Croatia VAT Number Structure
- Format Rules and Pre-Validation Patterns
- Validation Methods and Their Limitations
- Integrating VAT Validation into Your Billing System
- Common Pitfalls and Compliance Misconceptions
- Choosing the Right Validation Strategy for Your Business
Understanding the Croatia VAT Number Structure
Croatia uses the Osobni identifikacijski broj, usually called the OIB, as an 11-digit domestic tax and identity identifier. For EU cross-border VAT transactions, the VAT ID is normally formed by placing the country prefix HR before that same 11-digit OIB. The resulting external format is HR12345678901, as described in the European VAT country code and number guidance.

That creates two inputs your system may encounter:
- OIB:
12345678901, the bare 11-digit domestic identifier. - Croatia VAT ID:
HR12345678901, the country code followed by the same 11 digits.
The bare OIB can be appropriate for domestic records or local documentation. It isn't the value your system should submit as a Croatian VAT number for an EU cross-border validation. For an intra-community B2B transaction, the full HR-prefixed form is the value that should be checked through VIES before your application applies a reverse-charge or VAT exemption workflow. The Croatia tax identification guide describes this relationship between the OIB and the VAT-facing identifier.
Why the distinction matters in billing
A checkout form often labels its field “VAT number,” while a Croatian buyer naturally enters the identifier they use every day, the OIB. If your application only accepts the international form, it may reject a valid 11-digit entry without explaining what needs to change.
A useful interface can accept the user's formatting variations, then normalize the value internally. If the input contains 11 digits, your application can add HR only when your business rules and customer context support that interpretation. It should still retain the original input for troubleshooting and avoid treating every 11-digit number as an eligible VAT registration.
Practical rule: Store the normalized VAT ID separately from the customer's original entry. That gives finance teams a traceable value while keeping validation logic deterministic.
Valid examples include HR12345678901 and, after normalization, hr 12345678901. Invalid examples include HR1234567890, which has too few digits, HR123456789012, which has too many, and 12345678901 when a VIES lookup requires the country prefix. A string such as HR-12345678901 may be recoverable through input normalization, but it shouldn't be sent to a strict remote validator without cleaning.
For a broader explanation of international identifier structures, see the VAT number format glossary. The key implementation decision remains the same: keep the domestic OIB concept distinct from the HR plus 11 digits value used for cross-border VAT checks.
Format Rules and Pre-Validation Patterns
A Croatian VAT ID has a fixed structure for VIES-oriented processing: HR followed by exactly 11 digits. The canonical regular expression is:
^HR\d{11}$
The anchors matter. Without ^ and $, a longer string containing a valid-looking fragment could pass. The structure and canonical example are also documented in EU VAT number formats.
Normalize first, validate second
Users paste identifiers with lowercase letters, spaces, or punctuation. Normalize those harmless presentation differences before applying the strict check:
- Trim leading and trailing whitespace.
- Convert letters to uppercase.
- Remove spaces, hyphens, and other separators your interface explicitly permits.
- Confirm the result matches
^HR\d{11}$. - Send only the normalized value to VIES or an API wrapper.
Don't strip arbitrary characters without logging the original value. Overly permissive cleanup can turn a mistyped identifier into a different identifier, which creates an audit problem.
JavaScript:
function normalizeCroatiaVat(value) {
return value
.trim()
.toUpperCase()
.replace(/[\s-]/g, "");
}
function isCroatiaVatFormatValid(value) {
return /^HR\d{11}$/.test(normalizeCroatiaVat(value));
}
Python:
import re
def normalize_croatia_vat(value: str) -> str:
return re.sub(r"[\s-]", "", value.strip().upper())
def is_croatia_vat_format_valid(value: str) -> bool:
return bool(re.fullmatch(r"HR\d{11}", normalize_croatia_vat(value)))
The regex doesn't prove that the number is registered, active, or associated with the company the buyer claims to represent. It only prevents malformed input from reaching a remote service. That distinction is important because a local check is a syntax gate, not an authoritative registration lookup.
Format examples
| Input | Valid? | Issue |
|---|---|---|
HR12345678901 |
Yes | Canonical HR prefix plus 11 digits |
hr 12345678901 |
Yes after normalization | Lowercase prefix and space require cleanup |
HR-12345678901 |
Yes after normalization | Hyphen requires cleanup |
12345678901 |
No for strict VAT input | Bare OIB lacks the HR prefix |
HR1234567890 |
No | Payload is too short |
HR123456789012 |
No | Payload is too long |
HR12345A78901 |
No | Payload contains a non-digit |
Leading zeros deserve no special treatment. The payload is a fixed-width string, not a number for arithmetic. Keep it as text in JavaScript, Python, SQL, and JSON. Converting it to an integer can remove a leading zero and permanently alter the identifier before validation.
Engineering choice: Run the format check in the browser for immediate feedback, then repeat it on the server. Client-side validation improves the user experience, but only the server should decide whether the value reaches your invoicing and tax logic.
This pre-check also saves unnecessary remote calls. A malformed input can receive an immediate field-level error, while only structurally plausible values proceed to VIES. That reduces dependency traffic and keeps a slow external response from handling mistakes your own application can identify locally.
Validation Methods and Their Limitations
A Croatian VAT ID can pass a local pattern check and still fail an authoritative lookup. Production billing systems therefore combine local format validation, VIES, and a third-party API wrapper, assigning each method a clear role.

VIES
VIES is the EU's official VAT information exchange system and the appropriate authority for checking whether an HR-prefixed Croatian VAT ID is recognized for an intra-EU transaction. Its integration is less convenient than its authority suggests. The service uses SOAP, response times vary, and availability failures do not fit neatly into a synchronous checkout.
A negative or missing result also needs careful handling. EU guidance explains that a number may require confirmation from national authorities through the official VIES guidance. The outcome may indicate an unregistered number, a temporary service problem, or a registration requiring further confirmation. Store the response status and timestamp instead of reducing every failure to invalid.
Local format checks
A local check works without network access and gives immediate feedback for a missing HR prefix, an incorrect digit count, or characters outside the expected pattern. It belongs in the checkout flow and at the first server-side boundary.
Its limitation is registration status. A regex can confirm that the value has the expected Croatian shape, but it cannot establish whether the identifier is active or accepted for the intended transaction. A domestic OIB and the HR-prefixed VAT ID also need separate treatment. Normalize the customer's input before checking it, then retain the canonical VAT ID for remote validation.
Third-party wrappers
A managed API can expose a REST interface instead of SOAP and return consistent JSON. It may also provide caching, normalized errors, retry controls, and monitoring. Those features reduce integration work, but they add a vendor dependency and service cost. Evaluate the wrapper as part of the billing path, not as an invisible replacement for VIES.
| Method | Best use | Main weakness |
|---|---|---|
| VIES directly | Authoritative verification with full internal control | SOAP integration and availability handling remain yours |
| Local format check | Immediate feedback and malformed-input rejection | Cannot confirm registration |
| Managed API wrapper | Checkout integration and operational resilience | Adds dependency and vendor cost |
Direct VIES access suits batch processing when a back-office queue can review failures. It is less suitable for a checkout request that can wait indefinitely on an external dependency. Use a local pre-check, a bounded remote request, and an explicit verification_unavailable state. Do not treat a timeout as proof that the Croatian VAT number is invalid.
The VIES validation integration guide provides a useful comparison of implementation concerns. The practical decision is whether the system can preserve the compliance outcome and customer experience while the authoritative service is temporarily unreachable.
Integrating VAT Validation into Your Billing System
A resilient checkout treats VAT validation as a small workflow, not a single boolean field. Capture the customer's original entry, normalize it, reject impossible formats locally, call the remote validator with a timeout, and persist the result alongside the invoice decision.

A practical request path
For a Node.js service, the remote call should be isolated behind a server-side function. Don't call VIES directly from the browser, and don't let an unbounded request hold a checkout open.
async function validateVat(vatId) {
const response = await fetch("https://api.example.com/vat/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ vat_number: vatId }),
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
throw new Error(`VAT validation failed: ${response.status}`);
}
return response.json();
}
The endpoint above is illustrative. Your production code should use the provider's documented URL, authentication method, and response schema. The application should distinguish vat_invalid from service_unavailable. The first can block an exemption decision, while the second indicates that the system couldn't complete an authoritative check.
For Python, keep the same separation between normalization, transport, and tax decision:
import requests
def validate_vat(vat_id: str) -> dict:
response = requests.post(
"https://api.example.com/vat/validate",
json={"vat_number": vat_id},
timeout=5,
)
response.raise_for_status()
return response.json()
Cache successful validation results for 24 hours, a strategy described in TaxID's publisher information, but define the cache key carefully. Use the normalized country and VAT ID, and save the validation timestamp, status, returned company name, returned address, and the source response identifier if available.
A practical record might include:
customer_idvat_id_originalvat_id_normalizedcountry_codevalidation_statusvalidated_atvalidation_providerfailure_codeinvoice_tax_decision
The cache should reduce repeated calls for the same customer and invoice workflow. It shouldn't become a permanent assumption that a company remains registered forever.
Handling downtime and invoice creation
If the validator returns service_unavailable, don't rewrite the result as vat_invalid. Save the pending state, keep the customer informed, and choose a policy that matches your risk tolerance. You may continue checkout while withholding the VAT exemption, or place the order into a review queue before issuing the final invoice. The correct choice depends on your tax process, but granting a reverse charge during an outage is a poor default.
With Stripe, collect the VAT ID in your own checkout or customer tax fields, then apply the tax decision only after your server receives the validation result. Store the normalized value in your internal customer record and copy the verified decision into the invoice-generation event. If your team also needs structured document production, a resource on how to automate invoices with EDocGen can help connect validated customer data to invoice workflows.
For a fuller checkout architecture, use this VAT API checkout integration guide. The implementation should remain idempotent, so retries don't create duplicate invoices or contradictory tax decisions.
The workflow can be demonstrated separately from the article's code examples:
Common Pitfalls and Compliance Misconceptions
A checkout can receive the correct digits in the wrong field. A customer enters the bare OIB, the application sends it to VIES as a complete VAT ID, and the check fails. The solution is not to accept every 11-digit value. Normalize the entry, add the HR prefix when the international VAT ID is required, and store the domestic OIB separately from the cross-border identifier.
A valid result has a narrow meaning
A successful VIES response confirms that the submitted identifier was recognized for that validation request. It does not establish that the business is active, solvent, trading under the displayed name, or correctly matched to every company detail supplied by the buyer.
That distinction matters during supplier onboarding and higher-risk transactions. If the result is missing or inconsistent, request supporting information and use relevant national authority or registry checks. As noted in the validation methods section, the EU points users toward national authorities when VIES cannot confirm a number.
Don't turn outages into invalid customers
A timeout, malformed SOAP response, or provider outage is an infrastructure condition, not evidence that a Croatian VAT number is invalid. Model at least three outcomes:
- Valid: The remote service confirmed the identifier.
- Invalid: The remote service completed the check and rejected the identifier.
- Unavailable or pending: The system could not obtain an authoritative result.
Each state should produce different product behavior. Invalid can display a correction message and block a VAT-exempt invoice. Unavailable should start retry or review handling. Combining them creates false rejections and sends support teams after customer errors that are service failures.
Keep both identifiers through the billing flow. Storing only the bare OIB can leave the invoicing service without the HR-prefixed value needed for a cross-border validation record. Save the original entry, normalized VAT ID, validation timestamp, result, and company details returned by the provider. These records give auditors and engineers the context needed to understand which value was checked and what the system knew when it made the tax decision.
Choosing the Right Validation Strategy for Your Business
Choose validation depth according to transaction risk, workflow timing, and engineering capacity.
A small internal process may use a local format check followed by manual confirmation. That can be reasonable when staff review invoices before release and a remote result isn't needed synchronously. A customer-facing SaaS checkout has a different requirement. It needs fast local feedback, bounded remote calls, clear unavailable states, and durable logs.
Building a direct VIES wrapper gives you control over requests, storage, and retry behavior. It also leaves your team responsible for SOAP parsing, service failures, monitoring, security updates, and every edge case around response handling. A managed API reduces that maintenance burden and can provide REST responses, standardized errors, and caching, but it adds a vendor relationship and recurring infrastructure cost.
Use this checklist:
- Normalize: Accept common user formatting, then store a canonical
HRplus 11-digit value. - Pre-check: Reject malformed input before any network request.
- Verify: Use VIES or an equivalent authoritative service when the tax decision depends on registration status.
- Degrade safely: Separate invalid responses from unavailable responses.
- Cache deliberately: Set an expiry policy and retain the validation timestamp.
- Audit decisions: Record the value checked and the invoice outcome.
- Reconcile identity: Compare returned business details with the customer record when the transaction warrants it.

TaxID provides a REST API for VAT and company identifier validation, including Croatian VAT IDs, with format checks, cached lookups, and machine-readable outcomes. If you want to remove direct SOAP handling from your billing code, visit TaxID, test the Croatian validation flow, and connect the result to your checkout and invoice audit trail.