VAT Validation for Shopify: Coverage Gaps

Shopify has a native, built-in feature for validating EU and UK VAT numbers at checkout. For a store selling standard B2B orders through Shopify's own checkout, it works well and needs no extra integration. This guide is not about replacing it. It is about the parts of a real Shopify business that the native feature does not reach: custom and headless storefronts, wholesale channels, an existing customer base that was never validated, non-EU/UK tax IDs, and audit-proof evidence trails.

What Shopify's native VAT validation covers

Merchants using Shopify Tax to calculate tax in the EU or UK can turn on the "Company VAT number" field in checkout settings. When a customer enters a VAT number, Shopify validates it automatically and applies the reverse charge exemption on eligible cross-border orders. Valid numbers are saved to the customer profile for future orders.

Three conditions define the scope of this feature, straight from Shopify's own documentation:

  • It runs on guest checkout or Shop Pay checkout for orders shipped within the EU or UK.
  • It requires the store to be using Shopify Tax to calculate tax for that region.
  • If VIES cannot complete validation when the customer submits the number, the reverse charge exemption is not applied for that order.

That is a solid default for a store selling B2B goods or digital services through a standard Shopify checkout to EU and UK customers. Outside that path, the feature has real gaps.

Five gaps in Shopify's native VAT validation

No B2B-specific checkouts

Shopify says it directly: VAT validation "isn't currently available for B2B-specific checkouts." If you run Shopify's B2B feature for wholesale companies and locations, the checkout that those buyers use does not run the native VAT field at all. Wholesale is exactly where B2B VAT validation matters most, and it is exactly what the built-in feature excludes.

No headless or fully custom checkouts

A headless storefront built with Hydrogen typically hands off to Shopify's own hosted checkout for payment, so the native VAT field still applies there under the same guest/Shop Pay, EU/UK-only rules above. But once a store moves to a fully custom checkout, whether through checkout extensibility on Shopify Plus or a purchase flow built entirely on the Storefront and Admin APIs, there is no Shopify-hosted checkout page left to run the field on. The VAT number has to be collected and validated in your own code.

No coverage outside the EU and UK

The feature validates EU VAT numbers and UK VAT numbers only. If you sell to businesses in Switzerland, Norway, or Australia, Shopify has no equivalent check. See the Swiss VAT validation guide, Norwegian VAT validation guide, or Australian GST validation guide for the details of each registry. Avatcado validates all of these through the same endpoint as EU and UK numbers.

No revalidation over time

Shopify validates a VAT number once, when it is entered at checkout, and saves it to the customer profile. Nothing rechecks it later. A subscription customer whose VAT registration is deregistered six months after their first order keeps their reverse-charge status indefinitely unless someone revalidates manually.

No audit trail beyond the order

Shopify records that a VAT number was entered and validated at the time of the order, but it does not surface a VIES or HMRC consultation number, the reference tax authorities recognize as proof that a specific check ran on a specific date. For an audit, "the checkout accepted it" is weaker evidence than a stored consultation number tied to your own VAT ID as the requester.

When to use Avatcado alongside Shopify

  • You run Shopify B2B wholesale channels, where the native VAT field does not run
  • You have a headless or custom checkout built outside Shopify's hosted checkout page
  • You sell to Swiss, Norwegian, or Australian businesses
  • You need to revalidate an existing customer base periodically, not just at first order
  • You need a consultation number on file as audit-proof evidence, not just a passed checkout field

Wiring Avatcado into a Shopify workflow

A common pattern for wholesale or headless stores: collect the VAT number on your own signup or onboarding form (since Shopify's native field will not run for that channel), write it to a customer metafield, then validate it and tag the customer when the customers/create webhook fires. The Admin API's Customer object has a taxExempt boolean field (tax_exempt in the REST Admin API) for exactly this purpose.

import crypto from "node:crypto";
import Avatcado from "@avatcado/node";

const avatcado = new Avatcado(process.env.AVATCADO_API_KEY!);
const SHOP = process.env.SHOPIFY_SHOP_DOMAIN!; // "your-store.myshopify.com"

function isValidShopifyWebhook(rawBody: string, hmacHeader: string): boolean {
  const digest = crypto
    .createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET!)
    .update(rawBody, "utf8")
    .digest("base64");
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader));
}

// Handles the customers/create webhook. The VAT number comes from your own
// wholesale signup form (not Shopify's native checkout field, which does not
// run for B2B-specific or headless checkouts) and was already written to a
// "custom.vat_number" customer metafield before this webhook fires.
export async function handleCustomerCreate(rawBody: string, hmacHeader: string) {
  if (!isValidShopifyWebhook(rawBody, hmacHeader)) {
    throw new Error("invalid Shopify webhook signature");
  }

  const customer = JSON.parse(rawBody) as { admin_graphql_api_id: string };
  const vatNumber = await getVatNumberMetafield(customer.admin_graphql_api_id);
  if (!vatNumber) return;

  const { data, error } = await avatcado.vat.validate({
    vatNumber,
    requesterVatNumber: process.env.SELLER_VAT_NUMBER,
  });

  if (error || !data.data.valid) return; // leave tax_exempt untouched, charge VAT as normal

  await shopifyAdminGraphQL(
    `mutation TagTaxExempt($input: CustomerInput!) {
      customerUpdate(input: $input) {
        customer { id taxExempt }
        userErrors { field message }
      }
    }`,
    {
      input: {
        id: customer.admin_graphql_api_id,
        taxExempt: true,
        // Consultation number becomes your audit trail, stored alongside the exemption.
        metafields: [
          {
            namespace: "custom",
            key: "vat_consultation_number",
            type: "single_line_text_field",
            value: data.data.consultationNumber ?? "",
          },
        ],
      },
    },
  );
}

async function shopifyAdminGraphQL(query: string, variables: unknown) {
  return fetch(`https://${SHOP}/admin/api/2026-07/graphql.json`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Access-Token": process.env.SHOPIFY_ADMIN_ACCESS_TOKEN!,
    },
    body: JSON.stringify({ query, variables }),
  });
}

Passing your own VAT number as requesterVatNumber is what makes data.data.consultationNumber come back on the response, for EU and UK targets. Store it wherever your audit records live, whether that is a customer metafield as above, an order note, or your own database. It is only available for EU (VIES) and UK (HMRC) validations, not for Swiss, Norwegian, or Australian numbers.

Subscribe to the customers/create webhook topic (CUSTOMERS_CREATE in the GraphQL Admin API) from your app, and Shopify signs every delivery with an X-Shopify-Hmac-Sha256 header so you can verify it came from Shopify before trusting the payload.

Revalidating your existing customer base

If you have been running a Shopify store for a while, some fraction of the VAT numbers on file were validated once, months or years ago, and never checked again. Before backfilling, export the VAT numbers from customer metafields or your order history, then batch-validate them (up to 50 numbers per request on Pro and Business plans) rather than looping single requests. For customer bases in the hundreds or thousands, the async validation and webhooks guide covers submitting a large batch and receiving results without holding open requests. Re-run this quarterly or monthly depending on churn, and only flip a customer's tax_exempt flag off (or back to charging VAT going forward) after a failed revalidation, not automatically mid-cycle for an already-invoiced order.

Get started

Avatcado's free tier includes 500 validations per month with no credit card required. Add validation coverage to whatever part of your Shopify setup the native checkout field does not reach.

Start validating for free →

Read the API documentation for integration details, or see the SaaS billing guide for the broader validate-then-bill pattern this workflow follows.

Frequently asked questions

Does Shopify validate customer VAT numbers automatically?

Yes, but within a specific scope. Merchants using Shopify Tax to calculate tax in the EU or UK can enable the Company VAT number field in checkout settings; when a customer enters a number there, Shopify validates it automatically, applies the reverse charge exemption on eligible cross-border orders, and saves valid numbers to the customer profile for future orders. Three conditions from Shopify's own documentation define the boundary: it runs on guest or Shop Pay checkout for orders shipped within the EU or UK, it requires Shopify Tax for that region, and if VIES cannot complete validation at the moment the customer submits the number, the exemption is simply not applied for that order. Outside that path the feature does not reach: B2B-specific wholesale checkouts, fully custom or headless checkouts, Swiss, Norwegian, and Australian tax IDs, revalidation of numbers over time, and consultation-number audit trails all need separate handling.

Does Shopify's VAT validation work for B2B wholesale checkouts?

No. Shopify's documentation says it directly: VAT validation isn't currently available for B2B-specific checkouts, so if you use Shopify's B2B feature with wholesale companies and locations, the checkout those buyers go through never runs the native VAT field. The irony is that wholesale is exactly where B2B VAT validation matters most, since these are precisely the customers whose orders you want to zero-rate under the reverse charge. The workable pattern for wholesale channels: collect the VAT number on your own signup or onboarding form, write it to a customer metafield, and validate it when the customers/create webhook fires, verifying the X-Shopify-Hmac-Sha256 signature before trusting the payload. On a valid result, set the customer's taxExempt flag through the Admin API and store the consultation number in a metafield as your audit trail; on an invalid result, leave taxExempt untouched so the buyer is charged VAT as normal.

Can I validate VAT numbers on a headless Shopify storefront?

It depends on where checkout actually happens. A headless storefront built with Hydrogen typically still hands off to Shopify's hosted checkout for payment, and in that case the native VAT field applies under the same rules as any other store: guest or Shop Pay checkout, EU or UK orders, Shopify Tax enabled. But once you move to a fully custom checkout, whether through checkout extensibility on Shopify Plus or a purchase flow built entirely on the Storefront and Admin APIs, there is no Shopify-hosted checkout page left for the field to run on, so VAT collection and validation move into your own code. That means calling a standalone API like Avatcado from your checkout backend, deciding the tax treatment from the result before payment is taken, and writing the outcome back to Shopify (the taxExempt flag plus a metafield holding the consultation number) so downstream orders and reporting reflect the decision.

How do I revalidate VAT numbers for an existing Shopify customer base?

Export the VAT numbers from customer metafields or order history first, then batch-validate them instead of looping single requests: the batch endpoint takes up to 50 numbers per request on Pro and Business plans with per-item results, and for customer bases in the hundreds or thousands, async validation accepts up to 200 numbers per submission on Pro and 1,000 on Business, delivering results by webhook so you never hold a request open. This matters because Shopify validates a number exactly once, at checkout, and never rechecks it; a subscription customer deregistered six months after their first order keeps reverse-charge status indefinitely unless you intervene. Re-run the sweep quarterly or monthly depending on churn. Handle failures conservatively: only flip a customer's tax_exempt flag off (or start charging VAT going forward) after a failed revalidation, not automatically mid-cycle for an already-invoiced order, and record when and why each flag changed.

Sources

Try it on a real VAT number

Check any VAT or GST number against the official registry for free, no account needed.

Related guides