vatverify home
All guides

BigCommerce VAT validation: customer groups, webhooks, and tax exemption

Validate EU VAT numbers on a BigCommerce store, route cross-border B2B customers into a tax-exempt customer group, and persist the audit trail. Built around BigCommerce V3 API and webhooks.

TL;DR

  • Capture the VAT number on registration via a BigCommerce form field (or a custom Stencil widget).
  • A small server-side app subscribes to the store/customer/updated webhook, calls vatverify, and moves valid B2B customers into a "Tax-exempt B2B" customer group.
  • Validation results live as customer attributes so the audit trail is queryable from the admin panel and survives a re-validation cycle.

The BigCommerce VAT landscape

BigCommerce ships with built-in tax classes and customer groups. For EU VAT validation, the relevant pieces are:

  1. Customer groups: each customer can belong to one group; groups can have category-level discounts and price-list overrides. They cannot directly toggle tax exemption, but they can map to a tax class via the tax-zone configuration.
  2. Tax zones: BigCommerce calculates tax based on the buyer's address and the store's configured tax zone. A tax zone can include or exclude specific customer groups.
  3. Customer attributes and customer attribute values: free-form metadata you can attach to any customer. The right place to store the VAT number plus the validation timestamp.
  4. BigCommerce B2B Edition is the dedicated B2B layer with company accounts, sales reps, and quote management. The pattern below works for both B2B Edition and a standard BigCommerce store.

Architecture overview

The flow has four moving parts:

  1. A registration form field captures the customer's VAT number. Either a Stencil widget on the registration page or a B2B Edition company-onboarding field.
  2. A server-side app subscribes to the store/customer/updated webhook, validates via vatverify, and writes the result.
  3. The BigCommerce V3 API assigns the validated B2B customer to the "Tax-exempt B2B" customer group, which the tax-zone configuration treats as exempt from EU VAT.
  4. A re-validation job runs monthly to catch customer deregistrations.

Capturing the VAT number

Add a custom field to the registration form by extending the registration form schema in BigCommerce admin:

Settings → Account creation → Customize the form
Add field: "EU VAT number" (text)
Field name: vat_number

This stores the value as a form_fields entry on the customer object. The field is then visible on the customer's account profile and editable by the customer or the merchant.

Alternatively, on a Stencil-themed store, add a styled input to templates/components/account/login-form.html and POST to a custom app endpoint that updates the customer attribute via the V3 API.

The validation app

The webhook payload contains a customer ID; the app re-fetches the full customer record, reads the VAT number from the form field, validates, and writes back.

app/routes/webhooks.customer-updated.ts
import { Vatverify } from '@vatverify/node';

const vat = new Vatverify(process.env.VATVERIFY_API_KEY!);
const BC_API = `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}/v3`;
const BC_HEADERS = {
  'X-Auth-Token': process.env.BC_API_TOKEN!,
  'Content-Type': 'application/json',
};
const TAX_EXEMPT_GROUP_ID = Number(process.env.BC_TAX_EXEMPT_GROUP_ID);

export async function POST(req: Request) {
  const event = await req.json();
  const customerId = event.data.id as number;

  const customerRes = await fetch(
    `${BC_API}/customers?id:in=${customerId}&include=formfields,attributes`,
    { headers: BC_HEADERS },
  );
  const { data: [customer] } = await customerRes.json();
  if (!customer) return new Response();

  const vatField = customer.form_fields?.find(
    (f: { name: string; value: string }) => f.name === 'vat_number',
  );
  const vatNumber = vatField?.value as string | undefined;
  if (!vatNumber) return new Response();

  const result = await vat.validate({ vat_number: vatNumber });
  const valid = result.data?.valid === true;
  const buyerCountry = result.data?.country ?? null;
  const isCrossBorderB2B =
    valid && buyerCountry && buyerCountry !== process.env.SHOP_HOME_COUNTRY;

  await fetch(`${BC_API}/customers`, {
    method: 'PUT',
    headers: BC_HEADERS,
    body: JSON.stringify([
      {
        id: customerId,
        customer_group_id: isCrossBorderB2B ? TAX_EXEMPT_GROUP_ID : 0,
      },
    ]),
  });

  await fetch(`${BC_API}/customers/attribute-values`, {
    method: 'PUT',
    headers: BC_HEADERS,
    body: JSON.stringify([
      {
        customer_id: customerId,
        attribute_id: Number(process.env.BC_ATTR_VAT_VALIDATION_ID),
        value: JSON.stringify({
          vat_number: vatNumber,
          valid,
          country: buyerCountry,
          checked_at: new Date().toISOString(),
          consultation_number: result.data?.consultation_number ?? null,
        }),
      },
    ]),
  });

  return new Response();
}

Two design choices worth flagging:

  • The customer attribute is the audit trail. A B2B audit asks "was this customer's VAT number valid on the day of the supply?" A JSON-encoded attribute with checked_at and consultation_number answers that question without a separate database. vatverify also retains the same record on its own audit log on Pro and Business plans.
  • Customer-group assignment is the lever, not a per-customer tax flag. BigCommerce ties tax exemption to the group, so flipping the group is what tells the tax engine to skip EU VAT on this customer's orders.

Tax-zone configuration

In BigCommerce admin, go to Settings → Tax → Manual tax setup. For each EU tax zone:

  1. Set the rate as the destination country's standard VAT rate.
  2. Under "Tax exemption", exclude the "Tax-exempt B2B" customer group.

For Shopify-style automatic EU tax management, BigCommerce also offers Avalara AvaTax integration. AvaTax does not validate VAT numbers; you still need vatverify to decide whether the customer belongs in the exempt group. The AvaTax integration then respects the group assignment.

Re-validation

A scheduled job once a month is sufficient for most stores. Loop over the tax-exempt customer group, re-validate each VAT number, and demote any that fail back to the default group.

app/jobs/revalidate-b2b.ts
async function revalidateAllB2bCustomers() {
  let page = 1;
  while (true) {
    const res = await fetch(
      `${BC_API}/customers?customer_group_id=${TAX_EXEMPT_GROUP_ID}&page=${page}&limit=250&include=formfields`,
      { headers: BC_HEADERS },
    );
    const { data, meta } = await res.json();
    if (data.length === 0) break;

    for (const customer of data) {
      const vatField = customer.form_fields?.find(
        (f: { name: string }) => f.name === 'vat_number',
      );
      if (!vatField) continue;
      const fresh = await vat.validate({ vat_number: vatField.value });
      if (fresh.data?.valid !== true) {
        await fetch(`${BC_API}/customers`, {
          method: 'PUT',
          headers: BC_HEADERS,
          body: JSON.stringify([{ id: customer.id, customer_group_id: 0 }]),
        });
      }
    }

    if (page >= meta.pagination.total_pages) break;
    page += 1;
  }
}

Run it from a host you control (Vercel cron, GitHub Actions, a Render cron job, anything that can hit an authenticated endpoint on a schedule).

Cross-border vs domestic supplies

A B2B customer in the same country as the shop is a domestic sale. Domestic B2B is not reverse-charge under EU rules; the seller charges the standard rate even with a valid VAT number. The cross-border check (buyerCountry !== sellerCountry) is what decides whether the customer belongs in the tax-exempt group.

For a UK shop selling to an EU customer post-Brexit, the supply is a third-country export (zero-rated for UK VAT, import VAT due in the EU destination country). Same group assignment on the BigCommerce side; different legal basis.

What this guide does not cover

  • OSS and IOSS for B2C cross-border distance sales. Different scheme, different evidence requirements.
  • Domestic-reverse-charge sectors (construction, scrap metal, integrated circuits in several EU countries). Same-country B2B with these supply types follows the local domestic-reverse-charge rule, which BigCommerce's tax engine does not model.
  • Northern Ireland XI numbers for goods trade with the EU. The customer should provide their XI prefix number; vatverify routes XI through VIES.

Validate VAT in three lines.

Free up to 500 requests per month. No credit card.

Start free