Guide21 min readTaxID Team

VAT VIES Check: A Developer's Guide to EU VAT Validation

Learn how to perform a VAT VIES check correctly. This guide covers the VIES API, error handling, caching, and code examples for a resilient implementation.

vat vies checkeu vat validationvies apistripe vattaxid api

You're probably here because you need a vat vies check inside a real product, not inside a demo script. A customer enters a VAT number at checkout, or your billing system needs to decide whether to apply reverse charge, and suddenly you're staring at VIES. On paper, it sounds simple. Validate the number against the EU system and move on.

That's not how it goes in production.

What starts as “just call the official service” usually turns into SOAP parsing, country-specific quirks, weak error messages, and awkward decisions when the upstream service fails in the middle of a signup flow. I've seen teams lose more time hardening their VIES wrapper than they spent building the feature that made money.

Table of Contents

What is VIES and Why is Direct Integration a Trap

VIES is the EU's official system for checking whether a VAT number is valid for cross-border use. If you sell B2B in Europe, this isn't an optional side quest. It sits directly in your billing and compliance path.

The trap is assuming that because VIES is official, it's easy to build against.

A comparison chart showing the benefits of a robust VAT VIES validation solution over direct integration.

VIES is official and still painful

VIES is the source you ultimately have to consult, but direct integration pushes a lot of operational pain into your app. You're not dealing with a modern, predictable REST service. You're dealing with an old integration model that was never designed around developer ergonomics.

That matters less in a one-off admin tool. It matters a lot in checkout, invoicing, onboarding, and subscription changes.

For a basic definition, this VIES glossary entry is useful. The important engineering point is simpler: official doesn't mean production-friendly.

Practical rule: If a service decides tax treatment inside a live user flow, “it works most of the time” is not good enough.

Why DIY wrappers fail in production

The first version of a homemade wrapper often looks fine. It sends a request, gets a response, and returns valid or invalid. Then real traffic arrives.

Now you need to handle malformed input, inconsistent upstream failures, retries that don't spam the provider, logs that help support teams, and state transitions that don't break the user journey. You also need to decide what your app should do when the service is unavailable. Reject the sale? Queue the validation? Let the order proceed and flag it for review? A direct VIES integration doesn't answer any of that.

There's also the uptime problem. The European Commission's own help page notes that member state services can have significant downtime. It specifically shows that Germany's service can be unavailable for hours during maintenance windows that aren't always globally advertised, which can affect all B2B transactions relying on validation during that period, according to the European Commission VIES help page.

Here's where many teams make the wrong trade-off:

Approach Looks easy at first Gets expensive later
Direct SOAP integration Yes Yes
Thin in-house wrapper Yes Definitely
Managed validation API Usually Much less

A thin wrapper doesn't remove the hard parts. It just relocates them into your codebase. You still own caching, normalization, observability, retries, country-specific handling, and support tickets when finance asks why a valid customer couldn't complete checkout.

The Two Paths for VAT Number Validation

There are really two different checks people blur together when they talk about VAT validation. Treating them as the same thing is where bad architectures start.

A magnifying glass inspecting a VAT identification number connecting to a digital VIES API cloud service.

Local checks catch bad input fast

A local format check verifies whether the VAT number looks structurally plausible before you call anything external. That usually means checking things like:

  • Country prefix: Is the country code present and expected?
  • Character pattern: Does the body match the country's allowed format?
  • Basic normalization: Did the user paste spaces, lowercase characters, or separators you should clean up?
  • Checksum where applicable: Can you reject obviously broken input immediately?

This step is fast, deterministic, and cheap. It's also not enough.

A local pass only tells you the number is shaped correctly. It doesn't prove the number is currently valid in the official registry. That distinction matters because developers often overtrust regexes and checksum libraries.

Remote checks are authoritative but operationally messy

A remote VIES check asks the upstream registry whether the VAT number is valid. That's the authoritative lookup. If you need a true validation result for tax handling, this is the check that matters.

The downside is operational, not conceptual. Remote checks introduce latency, upstream dependency, and failure modes your UI has to survive. They also force your backend to distinguish between very different outcomes:

  • malformed input you should reject immediately
  • definitively invalid VAT numbers
  • temporary upstream outages
  • ambiguous cases where the result isn't available right now

A number can be well-formed and still fail remote validation. Your app has to tell the difference.

The approach that actually works

A thorough vat vies check uses both paths in sequence.

  1. Run a local format check first.
  2. Fail fast on obviously bad input.
  3. Call the remote validation service only for plausible numbers.
  4. Normalize the response into clean application states.

That ordering gives you better UX and cleaner backend behavior. It also keeps junk traffic away from the remote system.

Here's the decision logic side by side:

Validation type Speed Authority Best use
Local format check Immediate Not authoritative Input hygiene and fail-fast rejection
Remote VIES check Slower and failure-prone Authoritative Tax treatment and compliance decisions

What doesn't work is choosing only one. Local-only validation is fast but incomplete. Remote-only validation wastes calls on garbage input and makes the user wait for mistakes you could have caught on the client or at the API edge.

Performing a VAT VIES Check with a Modern API

If you're building this today, don't start with SOAP unless you enjoy debugging XML namespaces in a billing path. Use a modern REST API that wraps VIES and returns stable JSON.

One example is TaxID's guide to VAT number lookup, which shows the general pattern. The value isn't that it “connects to VIES.” Plenty of tools do that. The value is standardized request and response behavior that your application can depend on.

What a sane request flow looks like

The request should be boring:

  • send one tax ID string
  • get back a structured JSON response
  • branch on machine-readable fields
  • log the outcome
  • keep raw upstream weirdness out of your app

A good response shape usually includes these concepts:

Field concept Why it matters
Validation status Your app needs a clear valid or invalid outcome
Registered name Useful for invoice and account verification
Registered address Helps reconcile customer-submitted details
Standardized error code Lets you distinguish invalid input from outages

That's a much better interface than parsing brittle SOAP faults or trying to normalize country-specific edge cases yourself.

Nodejs example

const apiKey = process.env.TAXID_API_KEY;

async function validateVatNumber(vatNumber) {
  const response = await fetch("https://api.taxid.dev/validate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      tax_id: vatNumber
    })
  });

  const data = await response.json();

  if (!response.ok) {
    throw new Error(data.error?.code || "validation_failed");
  }

  return data;
}

(async () => {
  try {
    const result = await validateVatNumber("DE123456789");

    if (result.valid) {
      console.log("VAT number is valid");
      console.log("Company:", result.company_name || "N/A");
      console.log("Address:", result.company_address || "N/A");
    } else {
      console.log("VAT number is invalid");
    }
  } catch (err) {
    console.error("Validation error:", err.message);
  }
})();

The important part isn't the fetch call. It's that your code branches on a stable schema, not on transport weirdness.

Python example

import os
import requests

API_KEY = os.environ["TAXID_API_KEY"]

def validate_vat_number(vat_number: str):
    response = requests.post(
        "https://api.taxid.dev/validate",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "tax_id": vat_number
        },
        timeout=10
    )

    data = response.json()

    if not response.ok:
        code = data.get("error", {}).get("code", "validation_failed")
        raise RuntimeError(code)

    return data

try:
    result = validate_vat_number("FR12345678901")

    if result.get("valid"):
        print("VAT number is valid")
        print("Company:", result.get("company_name") or "N/A")
        print("Address:", result.get("company_address") or "N/A")
    else:
        print("VAT number is invalid")

except Exception as exc:
    print("Validation error:", str(exc))

This is the version you want junior engineers to copy into an internal service. It's obvious what happens on success and obvious what happens on failure.

Build your VAT validation integration so the application reads business states, not transport errors.

What to do with the response

Don't stop at valid: true.

Use the response to tighten your billing flow:

  • Pre-fill legal entity data: If you get a company name and address back, compare them to what the customer entered.
  • Store the validation event: Finance and support teams need a record that the check happened.
  • Separate invalid from unavailable: vat_invalid and service_unavailable should not produce the same user message.
  • Keep the API boundary clean: Return your own domain states to the frontend, such as validated, invalid, or pending_review.

What doesn't work is leaking upstream response complexity into your UI. Users shouldn't see cryptic validator output. Support teams shouldn't need to understand SOAP faults to explain why a VAT exemption wasn't applied.

Handling VIES Errors and Outages Gracefully

Most broken VAT validation implementations fail for one reason: they treat upstream errors as rare exceptions instead of normal operating conditions. In production, failures are part of the contract.

An infographic showing a step-by-step process for handling VIES API errors gracefully in a business system.

Treat failures as product behavior not exceptions

Your application should already know what to do in each of these cases:

  • Malformed VAT number
    Reject immediately. No retry. No remote call.

  • Confirmed invalid number
    Show a clear message and require correction.

  • Temporary service issue
    Don't pretend the number is invalid. Mark the validation as incomplete.

  • Country service outage
    Defer the final decision, log it, and route the account or invoice for follow-up.

A managed API usually helps because it converts messy upstream states into machine-readable codes such as service_unavailable. That gives your code something stable to act on.

For a closer look at resilience patterns around this problem, this article on VIES downtime resilience covers the operational side well.

After you've thought through the states, it helps to see the user-facing side of the problem in motion:

Caching changes everything

A lot of teams hesitate to cache VAT validations because they think “official lookup” means “must hit upstream every time.” That's not how resilient systems are built.

A cache does three jobs:

  1. It reduces repeated calls for the same VAT number.
  2. It makes common validation paths much faster.
  3. It shields your app from temporary upstream instability.

The hard part isn't adding Redis. The hard part is deciding your cache policy and making sure your app knows when a cached result is acceptable for the workflow in front of it.

A practical pattern looks like this:

Scenario Good behavior
Same customer returns to checkout Use cached validation if policy allows
Billing job reprocesses known account Prefer cached result, avoid unnecessary upstream calls
New VAT number appears Run local checks, then remote validation
Upstream fails during repeat lookup Fall back to cached result if you have one and mark review status clearly

Fallbacks that keep revenue moving

If VIES is down, don't force every affected user into a dead end.

A better flow is:

  • accept the VAT number entry
  • mark validation as pending
  • let the customer continue where business policy allows
  • queue a retry
  • flag the record for manual review if retries fail

That pattern works well in SaaS onboarding and B2B order flows because it separates commercial continuity from final compliance confirmation. You still need internal rules for when to issue an invoice, apply reverse charge, or hold fulfillment. But those are business decisions you control. They shouldn't be made accidentally by an upstream timeout.

If your fallback strategy is “show generic error, try again later,” you don't have a strategy. You have an outage amplifier.

Integrating VAT Checks into Your Checkout Flow

A VAT validation feature can be technically correct and still damage conversion if the checkout flow feels brittle. At this point, backend design and UX stop being separate concerns.

Where validation belongs in the user journey

In a typical SaaS checkout, the customer selects a business plan, enters billing details, and then adds a VAT number because they expect tax-exempt B2B treatment. That's the moment your system has to be helpful, not dramatic.

Put the VAT field where the user expects tax details, usually in the billing section after country selection. Don't hide it behind a modal or a separate settings page. The customer is already trying to tell you how the invoice should work.

The flow should behave like this:

  1. Customer selects an EU billing country.
  2. VAT field appears with light formatting help.
  3. User pastes the number in whatever messy format they have.
  4. Your frontend normalizes it automatically.
  5. Validation starts without blocking the entire form.
  6. UI shows a clear state: validating, valid, invalid, or temporarily unavailable.

What the user should see

The best checkout implementations keep feedback local and calm.

Use inline status near the VAT field, not a top-of-page error banner. If validation succeeds, show a simple confirmation and, if appropriate, the registered company name so the user trusts the result. If validation fails because the number is malformed or invalid, explain that directly. If the service is temporarily unavailable, say that verification is pending rather than blaming the user.

A few patterns work well:

  • Inline spinner: Lets the user keep filling the form while validation runs.
  • Auto-formatting: Remove spaces and normalize casing before sending the request.
  • Non-blocking submit: If your policy allows it, let checkout proceed while backend verification is pending.
  • Clear tax messaging: Tell the user whether VAT will be charged now or reviewed after verification.

What doesn't work is freezing the whole checkout on a remote tax lookup. That design makes your conversion rate depend on an external government-linked service you don't control. It's bad engineering and worse product design.

Your Production-Ready VAT Check Checklist

A vat vies check is production-ready when it behaves predictably under bad conditions, not when it passes a happy-path demo.

A ten-point checklist for implementing a production-ready system to validate VAT numbers using VIES API.

Shipping checklist

Use this before you wire VAT validation into billing, checkout, or invoicing:

  • Local validation exists so malformed numbers fail before any remote call.
  • Remote validation is isolated behind a single service boundary in your app.
  • Error states are standardized and distinct from each other.
  • Caching is in place for repeat lookups and resilience.
  • Retries use backoff instead of hammering the upstream service.
  • Fallback behavior is defined for temporary unavailability.
  • Validation events are logged with enough detail for support and audit work.
  • Frontend messaging is user-friendly and doesn't expose transport-layer noise.
  • Checkout isn't fully blocked by a transient validation failure unless policy requires it.
  • Tests cover bad input, invalid numbers, and upstream outages rather than only success cases.

One practical shortcut is to use a dedicated API that already wraps VIES with local format checks, standardized error codes, caching, and a clean REST response. That removes a lot of glue code and avoids turning tax validation into a maintenance project.


If you need VAT validation inside a real billing or checkout flow, TaxID gives you a developer-focused API for VIES and related tax ID checks without forcing you to own the SOAP wrapper, caching layer, and outage handling yourself.

AG
Alberto García

Founder, TaxID

Building EU VAT validation tools for developers. Obsessed with compliance automation and developer experience.