Validators
VAT format vs VIES: checksums are not registration
On this page 0%
A VAT number can be wrong in two different ways. The digits can fail the country’s checksum. Or the digits can be well-formed and still not belong to a trader in the registry. I keep mixing those up in conversation, so this post splits them. Randomly’s VAT validator is the first kind: format and checksum where the algorithm is public. VIES is the second kind: a live question to EU VAT systems.
If you only need a checkout field to reject “abc,” use the checksum. If you need to know whether a number is currently registered, you need VIES or the local tax authority, with all the downtime and Brexit footnotes that implies.
What a VAT id looks like
Most EU VAT numbers start with a country code, then a national identifier. Germany is DE plus 9 digits. France is FR plus 11 characters with a key. Spain mixes letters. The Netherlands has a B in a specific place. There is no single regex. I treat country as a first-class input. If you strip the prefix and run a generic digit check, you will bless garbage.
Checksums differ. Some countries use a weighted mod 11. Some use a variant of the same idea with different weights. France uses a key derived from the SIREN. I do not re-derive every algorithm from a napkin in this post. I do insist that a validator name the country, normalize case and spaces, then run that country’s rules. A function called isVat with one regex is a lie.
UK numbers are the footnote everyone skips. After Brexit, GB VAT is not an EU VIES story the way it was. The number shape still exists. HMRC has its own check. If your code still posts GB to the VIES SOAP endpoint and treats a fault as “invalid trader,” you are debugging diplomacy, not digits. Keep GB in a format catalog. Do not pretend Brussels will confirm it.
Checksums are typo catchers
A national checksum is like Luhn or IBAN mod-97: it catches mistypes, not fiction. You can generate a DE number that passes the math and still not be a GmbH. That is a feature for test fixtures. It is a hazard if you use the same function as “this company exists.”
I generate format-valid VAT-shaped strings the same way I generate IBANs: fill the body, compute the check characters, print. I do not scrape a registry to find a “real unused” number. Real unused is still real-adjacent. Format-valid is enough for a screenshot. Pair it with the honesty in PII-safe test data.
When a user transposes two digits, a decent checksum fails. When a user invents a number that happens to land on a valid check, it passes. That is the same limitation I accept for cards in Luhn test cards. Do not build a risk model on it.
What VIES actually is
VIES is the EU’s VAT Information Exchange System. You send a country-tagged number, you get back a validity flag and sometimes a name and address, depending on what the member state shares. It is a network call. It goes down. Member states can be “unavailable.” A timeout is not a no. A timeout is a timeout.
People wrap VIES with a 24-hour cache and then wonder why a newly registered trader looks invalid. People also hammer it from a serverless function on every keystroke. That is how you get blocked and then invent a local cache of positive hits that slowly becomes a stale yes-list.
I do not proxy VIES through Randomly. A public site that lets anyone query trader status is a scraping target and a privacy problem. Name and address of a company are not yours to bulk-collect because a form had a VAT field. The validator stays offline: structure, checksum, country length. If you need VIES, call it from your backend under your own rate limits and log the minimum.
UK after Brexit
GB VAT numbers still show up on invoices. Northern Ireland has extra paperwork I am not going to fake-expert. For a format tool, GB is a catalog entry with a checksum. For a live registry, you want HMRC’s checker, not VIES, unless you have a specific EU-side reason. I have seen checkout code that marked every GB VAT as invalid on 1 January 2021 because the VIES client threw. Users were still traders. The software was homesick.
If you store VAT numbers, store the country separately from the national number. Concatenated strings with optional spaces are how you get duplicates: GB 123 versus GB123. Compact for identity, pretty for display. Same sermon as IBAN. The arithmetic cousin is mod-97; the operational cousin is “stop putting spaces in the primary key.”
How I validate in a form
Client: strip spaces and punctuation, uppercase, require a known country prefix, require length, run checksum if we have one. Show a specific error: “Germany VAT should be 9 digits,” not “invalid.” Server: repeat. Then, if the business actually needs a registered trader, queue a VIES or HMRC lookup asynchronously. Do not block typing on a SOAP round trip.
If VIES returns invalid, do not immediately delete the user. Numbers get typed wrong. Registrations lag. Offer a path to upload a certificate or skip VAT for B2C. I have seen a B2B checkout that refused to take money because VIES was in maintenance. That is not compliance. That is a self-DDoS.
Test data
Use checksum-valid fakes in staging. Never copy a customer VAT from production into a ticket. If you must talk about a failure, talk about the country and the error code. The IBAN validator and VAT tools sit next to each other in my head because both are “looks official, is math.” Neither is a bank, neither is a tax office.
Some countries publish obvious test numbers. Prefer those in unit tests, plus a generated round-trip from your own checksum code. If generator and validator share a bug, you will only catch it with an official example. Same pattern as IBAN.
False friends
EORI is not VAT. TIN is not VAT. SIRET is not a VAT number even though French VAT embeds SIREN. If your form label says VAT and you accept any national business id, you will store a SIRET in a VAT column and then fail VIES forever. Label the field with an example: DE123456789.
Checksum absence is a country fact, not a TODO. If we do not have an algorithm, the validator should say “length and prefix only” rather than invent weights. Invented weights are worse than none.
What Randomly will not claim
We will not put a badge that says “registered trader” on a static check. We will not keep a mirror of VIES. We will not “validate UK via EU” as a compatibility hack. Format is the product. Live registry is your integration. That split is the same honesty as SSN validator: shape, not existence.
If you are generating other fiscal identifiers for the same fixture, keep them all fake. A real VAT next to a fake IBAN is still a leak. Mix tools, not worlds.
Frequently asked questions
Does a passing checksum mean I can reverse-charge?
No. Reverse charge and VAT ID validation are policy plus registry, not a mod 11.
Should I call VIES on every invoice?
Call it when the number is new or changed, then cache with a TTL you can defend. Every keystroke is how you get banned.
Why does GB fail VIES?
Because it is not an EU VAT number for that system anymore. Validate GB locally or via HMRC.
Does Randomly store VAT numbers I type?
The format tool runs in the page. Do not paste live customer numbers into a public box anyway. See privacy.