{ "shopify" :
Shopify B2B VAT validation for Dutch customers
Short answer
Shopify does not let you hang a live VAT check on the checkout page on most plans. Checkout UI extensions on the checkout page are Plus only, and Shopify Functions run without network access, so they cannot reach VIES. The workable route is validating before or after checkout through an app proxy and storing the result in a metafield.
# storefront -> your app
GET "/apps/kvk/lookup?kvk=68750110"
# your app, server-side
curl ".../v1/validate/vat/NL12..B01" \
-H "Authorization: Bearer KEY"
{
"number": "NL123456789B01",
"valid": true
}
What Shopify lets you script, and what it does not
This is the heart of every Shopify company-data integration, and most guides gloss over it: the checkout is closed.
| What you want | Possible | Condition |
|---|---|---|
| Field on the checkout page | Yes | Checkout UI extension, Shopify Plus |
| External API call from checkout | Yes | Same extension, with network access |
| External API call from a Function | No | Functions run with no network |
| Field on cart or account page | Yes | Plain theme, any plan |
| Check after the order | Yes | Webhook on orders/create |
A cart validation or checkout validation Function is WebAssembly without network access. It can never call VIES or our endpoint itself. What it can do is read a metafield or cart attribute you filled earlier and block the checkout when it says vatValid: false. That is the only path to a hard block inside checkout, and it requires the validation to have happened earlier.
Where the data belongs
- Cart attribute for the order in progress. It shows up as an additional detail on the order and travels to fulfilment.
- Customer metafield for repeat purchases, for example
custom.kvk_numberandcustom.vat_valid_at. Store a timestamp with it, or a year from now nobody knows what thattruestill means. - Company location if you run B2B. That is where the tax registration id belongs and where the exemption hangs.
- Order metafield for the evidence at order time. For e-invoicing and any later audit you want what you saw then, not what is true now.
The app proxy, concretely
An app proxy gives you a route under your own store domain that Shopify forwards to your server. The API key stays server-side and you avoid CORS entirely.
# shopify.app.toml
[app_proxy]
url = "https://your-app.example.com/proxy"
subpath = "kvk"
prefix = "apps"
The storefront calls /apps/kvk/lookup?kvk=68750110, Shopify forwards it to your endpoint with query parameters plus a signature. Always verify that signature with your app secret before you spend a lookup. If you skip it, your proxy is an open pipe into your quota for anyone who knows the path.
Your handler then makes the real call:
const r = await fetch(`https://api.kvkbase.nl/v1/lookup/${kvk}?enrich=true`, {
headers: { Authorization: `Bearer ${process.env.KVKBASE_KEY}` },
});
if (r.status === 404) return json({ ok: false, reason: "not_found" }, 200);
const c = await r.json();
return json({
ok: c.isActive,
name: c.statutoryName ?? c.name,
vat: c.vat?.number ?? null,
vatValid: c.vat?.valid ?? null,
});
Note the 200 on our 404. A front end that turns any non-2xx into a generic error tells the customer “something went wrong” when the actual answer is “this number does not exist”.
If all you want is input convenience on the cart or account page without building an app, the widget can go straight into your theme. It fills name, address and VAT fields from a search, using a public key. For the validation that counts, stay on the proxy.
What actually breaks in production
VIES is down. VAT checks travel through the member state service and it fails regularly. You get UPSTREAM_ERROR instead of a verdict. Block hard on that and your B2B checkout is offline for as long as a foreign government service is. Store unknown, let the order through with VAT charged, and re-evaluate in the webhook.
The customer enters the holding company’s VAT number. Inside a group, the holding is often known to VIES while the trading entity placing the order is not, or the reverse. The number validates, the company name does not match the shipping details. Keep both and surface the mismatch instead of trusting it blindly.
The backfill of existing customers stalls. Enriching thousands of customer records with individual calls hits the rate limit inside a minute. Use POST /v1/lookup/batch and process in blocks with a pause between them.
Cart attributes do not survive every session. If the customer clears the cart or returns in a new session, your attribute is gone while the customer is convinced they already filled it in. Write the validation to the customer metafield as soon as they log in, and repopulate the cart from there.
Frequently asked
Do you have a Shopify app?
Can I validate the VAT number inside checkout itself?
Why can a Shopify Function not do this?
Does a valid VAT number switch on the reverse charge automatically?
Updated:
One call gives you the whole company
KVK data, the derived VAT number and a live VIES check, in a single request. The free plan covers 50 lookups a month and needs no card.
- 50
- free lookups a month
- 1
- request instead of three
- 0
- cards, contracts or sales calls