Home / Blog / B2B VAT Exemption in EU SaaS: What Your Billing System Must Do

Compliance12 min read

B2B VAT Exemption in EU SaaS: What Your Billing System Must Do

Reverse charge sounds simple but the implementation details trip up most SaaS billing engineers. Here is what your system must do to stay compliant.


Building a SaaS product that sells to EU businesses means navigating a tax rule that is simple in principle and surprisingly complicated in practice: the EU reverse charge mechanism. The rule says that when a registered EU business buys from another EU business across borders, the seller does not charge VAT — the buyer accounts for it in their own country instead. But applying zero-rate treatment without correctly validating the buyer's VAT registration is a compliance failure, not a simplification. This guide walks through the complete billing implementation: how to collect VAT numbers, validate them, apply the correct tax treatment in your billing engine, generate compliant invoices, and handle the edge cases that trip up most engineering teams.

The EU Reverse Charge Mechanism: What It Actually Means

The reverse charge mechanism is an EU VAT rule that shifts the responsibility for accounting for VAT from the seller to the buyer in cross-border B2B transactions. When you (a German SaaS company) sell to a French company with a valid VAT number, you do not charge 19% German VAT on the invoice. Instead, you issue a zero-rate invoice and the French company accounts for the equivalent French VAT in their own VAT return — they both add it as output tax and deduct it as input tax, netting to zero. This is called 'self-accounting' or 'the buyer accounts for the tax'.

The mechanism exists to simplify cross-border VAT administration and prevent double taxation. Without it, EU businesses selling across borders would need to register for VAT in every country they sell to. With reverse charge, the seller can issue zero-rate invoices to verified B2B customers across all 27 EU member states without any additional VAT registration (unless they also have B2C sales above the OSS threshold of €10,000 per year). From a cash flow perspective, it is also advantageous — you are not floating VAT on behalf of your customer between invoice and payment.

The critical condition that unlocks reverse charge treatment is a valid VAT registration number. Without a valid number confirmed by VIES, the transaction is treated as B2C — you must charge VAT at the applicable rate for the buyer's country (under OSS rules) or your own country's rate if you are below the pan-EU threshold. There is no middle ground: either the buyer has a valid VAT registration that you have verified, or you charge VAT. Getting this wrong in either direction creates compliance exposure.

Warning

Applying zero-rate without VIES verification makes your company liable for the VAT. Charging VAT to a customer who provided a valid VAT number is also incorrect — it creates overpayment issues and potential VAT fraud liability in some jurisdictions. Validate every time.

Step 1: Collecting the VAT Number at Signup

The right time to collect a VAT number is at the billing details step of your signup flow — not after payment confirmation, not during the first invoice run. Collecting it at signup gives you the opportunity to validate it in real time and display the company name back to the user as confirmation, which significantly reduces input errors. Users who see 'Acme GmbH, Musterstraße 1, Berlin' displayed after entering their VAT number know immediately that the number is correct.

Make the VAT number field optional, not required. Not every business customer has a VAT registration — sole traders below the registration threshold, non-EU businesses, and companies in certain sectors may not be VAT-registered. Making it optional and validating only when provided is the correct UX pattern. Add a label that explains the field: 'EU VAT number (optional — for zero-rate B2B treatment)'. This reduces support tickets and sets the right expectation.

From a technical perspective, collect the VAT number as a plain text input and strip whitespace before validation. Users copy-paste VAT numbers from invoices and often include spaces, dots, or dashes. The TaxID API accepts numbers with or without spaces and normalises them internally, but cleaning on your side before submission is good practice. Store the number exactly as the API returns it in the vat field of the response — this is the normalised, canonical form.

tsxVatNumberInput.tsx
// React component for VAT number input with real-time validation
import { useState, useCallback } from 'react';

interface VatInputProps {
  onValidated: (vat: string, companyName: string | null) => void;
}

export function VatNumberInput({ onValidated }: VatInputProps) {
  const [value, setValue] = useState('');
  const [status, setStatus] = useState<'idle'|'checking'|'valid'|'invalid'|'unavailable'>('idle');
  const [companyName, setCompanyName] = useState<string | null>(null);

  const validate = useCallback(async (vat: string) => {
    if (vat.length < 8) return; // too short to be valid
    setStatus('checking');
    const res = await fetch('/api/validate-vat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ vat: vat.replace(/[\s.-]/g, '') }),
    });
    const data = await res.json();
    if (data.status === 'service_unavailable') {
      setStatus('unavailable'); // show soft message, don't block
    } else if (data.valid) {
      setStatus('valid');
      setCompanyName(data.company_name);
      onValidated(data.vat, data.company_name);
    } else {
      setStatus('invalid');
    }
  }, [onValidated]);

  return (
    <div>
      <input
        type="text"
        value={value}
        placeholder="DE123456789"
        onChange={e => { setValue(e.target.value); validate(e.target.value); }}
      />
      {status === 'valid' && companyName && (
        <p className="text-green-600 text-sm">✓ {companyName}</p>
      )}
      {status === 'invalid' && (
        <p className="text-red-500 text-sm">VAT number not found in VIES</p>
      )}
      {status === 'unavailable' && (
        <p className="text-yellow-600 text-sm">EU VAT system temporarily unavailable — we will verify shortly</p>
      )}
    </div>
  );
}

Step 2: Server-Side VAT Validation

Client-side validation (the React component above) is for UX feedback only — it is never authoritative for billing purposes. The real validation must happen server-side before you apply zero-rate treatment to any invoice. Never trust the client to tell you whether a VAT number is valid; validate it yourself from your backend using your own API key.

Create a server-side API route that validates the VAT number and returns the result. This route is called both from the signup component in real time and from your billing system before each invoice generation. The route should validate the number, store the result (see the complete validation guide for the storage schema), and return a clean response to the caller. Do not expose the TaxID API key to the frontend.

typescriptapi/validate-vat.ts
// Next.js App Router API route
export async function POST(request: Request) {
  const { vat, customerId } = await request.json();

  if (!vat || typeof vat !== 'string') {
    return Response.json({ error: 'vat_required' }, { status: 400 });
  }

  const country = vat.slice(0, 2).toUpperCase();

  const apiRes = await fetch(
    `https://taxid.dev/api/v1/validate/${country}/${vat}`,
    {
      headers: { Authorization: `Bearer ${process.env.TAXID_API_KEY}` },
      signal: AbortSignal.timeout(5000),
    }
  );

  if (!apiRes.ok) {
    return Response.json({ error: 'validation_failed' }, { status: 502 });
  }

  const data = await apiRes.json();

  // Store validation record if customerId provided
  if (customerId) {
    await db.vatValidation.create({ data: {
      customerId, vatNumber: data.vat, status: data.status,
      companyName: data.company_name, address: data.address,
      requestId: data.request_id, checkedAt: new Date(),
    }});
  }

  return Response.json({
    valid: data.valid,
    status: data.status,
    company_name: data.company_name,
    address: data.address,
    vat: data.vat,
  });
}

Step 3: Applying Zero-Rate in Your Billing Engine

Once you have a confirmed valid VAT number, you need to tell your billing engine to apply zero-rate treatment to this customer's invoices. The exact implementation depends on your billing provider. For Stripe Checkout integration, you update the customer's tax_exempt status and apply a tax ID. For Paddle, you set the customer's country and business flag. For custom billing systems, you store the tax_class field and apply the correct logic in your invoice generation code.

The key principle: the zero-rate applies to the customer account permanently (until their VAT registration changes), not just to the first transaction. Once you have validated a customer as B2B, tag their account in your database with vatStatus: 'b2b', vatNumber, vatValidatedAt, and companyName. Every subsequent invoice generation should read this status rather than re-calling the validation API on every invoice — but you should re-validate quarterly to catch deregistrations.

typescriptbilling-tax.ts
interface CustomerTaxProfile {
  taxClass: 'b2b' | 'b2c';
  vatNumber: string | null;
  vatValidatedAt: Date | null;
  companyName: string | null;
  countryCode: string;
}

function determineTaxTreatment(profile: CustomerTaxProfile): {
  chargeVat: boolean;
  vatRate: number | null;
  invoiceNote: string;
} {
  // B2B with valid VAT number in a different EU country
  if (
    profile.taxClass === 'b2b' &&
    profile.vatNumber &&
    profile.vatValidatedAt &&
    isEuCountry(profile.countryCode) &&
    profile.countryCode !== process.env.SELLER_COUNTRY
  ) {
    return {
      chargeVat: false,
      vatRate: null,
      invoiceNote: 'Reverse charge — VAT to be accounted for by recipient',
    };
  }

  // B2C or same country — charge VAT
  return {
    chargeVat: true,
    vatRate: getVatRate(profile.countryCode), // from /vat-rates/[country]
    invoiceNote: '',
  };
}

Step 4: Generating Compliant Invoices

A zero-rate B2B invoice must include specific elements to be legally compliant under EU VAT law. Missing any of these can invalidate the invoice for your customer's accounting purposes and expose you to audit risk. The required elements for a reverse charge invoice: your VAT registration number, the buyer's VAT registration number, a statement that reverse charge applies (the exact wording varies by country but 'Reverse charge — VAT to be accounted for by the customer' is accepted across all EU member states), and the net amount without VAT. Do not include a VAT line at all — even with 0% — as this can be misinterpreted.

Store all of these values at invoice generation time, not at payment time. Invoice data must be immutable once issued. If a customer's VAT status changes after you issue an invoice, the original invoice remains valid as-is — you issue a corrective invoice for subsequent periods, not a retroactive amendment. This is why storing the validation timestamp and the company name at the time of invoice generation matters: it proves the number was valid when the invoice was issued.

For the buyer's address on zero-rate invoices, use the registered address returned by VIES (the address field in the API response) rather than the shipping or billing address the customer entered. The VIES-registered address is the legally relevant one for reverse charge purposes. Some EU countries' tax authorities specifically check that the address on a zero-rate invoice matches the VIES registration data during audits.

Edge Cases Your Billing System Must Handle

Deregistration After Signup

A B2B customer who was validly registered at signup may deregister their VAT number months later — they close the business, restructure, or voluntarily deregister because they fell below the threshold. If you continue issuing zero-rate invoices to a deregistered customer, those invoices are incorrect and your company is liable for the VAT on them. This is why periodic re-validation matters: at minimum, re-validate all active B2B customers at the start of each billing quarter. If a re-validation returns inactive, switch the customer to B2C billing immediately for the next invoice cycle and notify them.

VIES Unavailable at Invoice Time

If VIES is unavailable when you attempt to re-validate before generating a quarterly invoice, do not block the invoice run. Use the last successful validation result if it is less than 30 days old — the risk of a business deregistering in a short window while VIES happens to be down is low and acceptable. If the last validation is more than 30 days old and VIES is unavailable, generate the invoice with a note in your system to re-validate and issue a correction if needed once VIES recovers. See VIES Downtime: How to Build a Resilient VAT Validation Flow for the full resilience strategy.

Customers Without a VAT Number

Not all business customers are VAT-registered. Small businesses below the national registration threshold, businesses in certain exempt sectors, and new businesses that have not yet received their VAT number are all valid B2B customers without a VAT number. For these customers, apply the standard B2C VAT treatment — charge VAT at the applicable rate. You cannot give them reverse charge treatment without a valid VAT number, regardless of their business status. Some SaaS companies offer a manual exception process for large enterprise customers pending VAT registration, but this requires legal review and explicit documentation.

Common B2B VAT Billing Mistakes

OSS Registration and Cross-Border B2C Sales

The One Stop Shop (OSS) is the EU's mechanism for simplifying VAT compliance on cross-border B2C digital services. If your SaaS has both B2B customers (zero-rated via reverse charge) and B2C customers (consumers without VAT numbers), you need to handle both regimes correctly. For B2C sales above €10,000 per year across all EU member states combined, you must either register for OSS in one EU country or register for VAT separately in each country where you have customers.

OSS does not change the B2B reverse charge rules — it only affects B2C. If a customer provides a valid VAT number, it is always B2B treatment regardless of your OSS registration status. If they do not provide one (or it is invalid), and the total cross-border B2C revenue exceeds €10,000 per year, OSS applies. The practical implementation for a SaaS billing system: classify each customer as B2B (valid VAT number confirmed via VIES) or B2C (no valid VAT number) at signup, and apply the appropriate regime to every invoice for that customer. The classification can change if a B2C customer later provides a valid VAT number — update their billing profile and apply B2B treatment from the next invoice cycle.

The €10,000 OSS threshold applies to the sum of all cross-border B2C sales to all 27 EU member states combined. It is not per-country. If you have €8,000 in German B2C sales and €3,000 in French B2C sales, you are over the threshold even though neither country individually exceeds it. Once over, all cross-border B2C sales require OSS or local registration — you cannot selectively apply it only to the countries where you have exceeded a local sub-threshold.

Implementation Checklist

Start validating EU VAT numbers

Free plan — 100 validations/month. No credit card required.

Related articles