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.
// 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.
// 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.
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
- →Applying zero-rate based on self-reported business status rather than VIES validation: a customer saying 'I am a business' is not sufficient. You need a validated VAT number.
- →Not storing the validated VAT number on the invoice: the customer's VAT number must appear on each zero-rate invoice for it to be legally valid for their accounting.
- →Forgetting the reverse charge statement on the invoice: 'Reverse charge — VAT to be accounted for by the customer' or equivalent must appear on zero-rate invoices.
- →Re-validating on every invoice rather than quarterly: unnecessary API calls. Validate at signup, then quarterly. Re-validate immediately on billing detail changes.
- →Not handling deregistration: implement a quarterly re-validation job that checks all active B2B customers and switches deregistered ones to B2C billing.
- →Applying zero-rate to same-country transactions: reverse charge only applies to cross-border EU transactions. If your company and the customer are in the same EU country, normal domestic VAT rules apply regardless of B2B status.
- →Using the customer's reported address instead of the VIES address on invoices: always use the VIES-registered address on zero-rate invoices to pass audit verification.
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
- →Add optional VAT number field to your billing/signup flow with clear label explaining its purpose.
- →Validate the VAT number server-side via TaxID API on submission — never trust client-side validation alone.
- →Store the full validation response (status, company_name, address, request_id, checkedAt) for audit compliance.
- →Tag the customer record as b2b or b2c based on validation result — handle service_unavailable as pending with re-validation queued.
- →Configure your billing engine (Stripe, Paddle, custom) to apply zero-rate for b2b customers in other EU countries.
- →Include the customer's validated VAT number and 'Reverse charge' statement on every zero-rate invoice.
- →Run a quarterly re-validation job for all active B2B customers to catch deregistrations.
- →Monitor the re-validation job output — switch deregistered customers to B2C billing and notify them.
- →Document your validation process for tax audit readiness — retain validation records for the legally required period (7-10 years in most EU jurisdictions).
Related guides on taxid.dev
VIES architecture, response fields, format validation, and code examples
Handle service_unavailable without blocking invoices or checkouts
Server-side zero-rate before payment intent creation
Complete implementation guide for subscription billing
Endpoint docs, all status codes, and error handling reference
Start validating EU VAT numbers
Free plan — 100 validations/month. No credit card required.