When an enterprise buyer in Germany or Italy enters a valid VAT number at B2B checkout, they expect a 0% intra-community reverse-charge invoice once cross-border supply conditions (sellerCountry !== buyerCountry) are satisfied.
Yet one of the most common distributed-systems bugs in European B2B commerce happens inside the European Commission's VIES REST API (POST https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number). When a Member State's national tax database hits its concurrency limit or undergoes maintenance, VIES returns HTTP 200 OK with actionSucceed: false and an errorWrappers array:
{
"actionSucceed": false,
"errorWrappers": [
{
"error": "MS_MAX_CONCURRENT_REQ"
}
]
}
Because the HTTP status is 200, standard HTTP retry middleware never triggers. And because data.valid is omitted (undefined) when actionSucceed is false (or mapped to valid: false in naive wrappers), any checkout handler that checks if (!res.ok || !data.valid) misclassifies a temporary government database outage as an unregistered VAT ID—and falsely charges 17%–27% VAT.
1. Classifying VIES Soft-Failures vs. Permanent Rejections
A production VIES integration must inspect errorWrappers[0].error before evaluating data.valid:
| VIES Response Class | Exact Error Codes / Payload Condition | Checkout & Fulfillment Action |
|---|---|---|
TRANSIENT_VIES_ERRORS |
MS_UNAVAILABLE (Member State Service Unavailable), MS_MAX_CONCURRENT_REQ (Member State Maximum Concurrent Requests), GLOBAL_MAX_CONCURRENT_REQ (Global Maximum Concurrent Requests), SERVICE_UNAVAILABLE, TIMEOUT, SERVER_BUSY, NETWORK_ERROR
|
Never block checkout (checkoutBlocked: false); place order in PENDING_ASYNC_RETRY (fulfillmentHold: true) with exponential backoff 60 * 2 ** (attempt - 1) up to MAX_VIES_ATTEMPTS = 4
|
PERMANENT_VIES_ERRORS |
INVALID_INPUT, INVALID_REQUESTER_INFO (Invalid Requester Information — seller requester VAT configuration error), VAT_BLOCKED
|
Place order in HOLD_INVALID_VAT (vat_audit:invalid_vat_id); flag INVALID_REQUESTER_INFO as SELLER_REQUESTER_CONFIG_ERROR
|
| Unregistered VAT |
valid: false with empty errorWrappers
|
Place order in HOLD_INVALID_VAT so finance can request a valid VAT ID or collect standard VAT |
| Verified + Consultation ID |
valid: true + non-empty requestIdentifier (consultationNumber) |
Approve VERIFIED_ZERO_RATED (fulfillmentHold: false) and archive SHA-256 consultation digest |
2. Prefix Normalization (Greece GR to EL, Northern Ireland XI Goods, Great Britain GB Exclusion) & 3-State Circuit Breaker
Before calling VIES, normalize ISO country prefixes: Greece uses ISO-3166 GR in billing forms, but VIES strictly requires EL (GR123456789 to EL123456789). Under the Windsor Framework, Northern Ireland B2B goods transactions use the XI prefix (27 EU Member States + XI = 28 VIES prefixes), whereas post-Brexit GB VAT numbers are excluded from VIES.
const TRANSIENT_VIES_ERRORS = new Set([
'MS_UNAVAILABLE',
'SERVICE_UNAVAILABLE',
'MS_MAX_CONCURRENT_REQ',
'GLOBAL_MAX_CONCURRENT_REQ',
'TIMEOUT',
'SERVER_BUSY',
'NETWORK_ERROR',
]);
const PERMANENT_VIES_ERRORS = new Set([
'INVALID_INPUT',
'INVALID_REQUESTER_INFO',
'VAT_BLOCKED',
]);
const MAX_VIES_ATTEMPTS = 4; // 1 initial + up to 3 async retries
export function evaluateViesConsultation({ data, rawCc, attempt = 1 }) {
const normCc = rawCc === 'GR' ? 'EL' : rawCc; // NI B2B goods use 'XI'; 'GB' is non-VIES
const errCode = data?.errorWrappers?.[0]?.error ?? '';
if (PERMANENT_VIES_ERRORS.has(errCode)) {
return { checkoutBlocked: false, fulfillmentHold: true, status: 'HOLD_INVALID_VAT', error: errCode, normCc };
}
if (!data || TRANSIENT_VIES_ERRORS.has(errCode) || typeof data.valid !== 'boolean') {
if (attempt >= MAX_VIES_ATTEMPTS) {
return { checkoutBlocked: false, fulfillmentHold: true, status: 'HOLD_MANUAL_REVIEW_RETRY_EXHAUSTED', attempt };
}
return {
checkoutBlocked: false,
fulfillmentHold: true,
status: 'PENDING_ASYNC_RETRY',
backoffSec: Math.min(3600, 60 * 2 ** (attempt - 1)),
};
}
if (data.valid === true) {
const consultationId = String(data.requestIdentifier || data.consultationNumber || '').trim();
return consultationId
? { checkoutBlocked: false, fulfillmentHold: false, status: 'VERIFIED_ZERO_RATED', consultationId }
: { checkoutBlocked: false, fulfillmentHold: true, status: 'VERIFIED_MISSING_CONSULTATION_ID' };
}
return { checkoutBlocked: false, fulfillmentHold: true, status: 'HOLD_INVALID_VAT' };
}
3. Why requestIdentifier Requires Seller Requester Credentials
To substantiate 0% intra-community supply during a tax audit, recording valid: true is not enough: you need the official VIES requestIdentifier (consultationNumber). VIES only populates requestIdentifier when your check-vat-number payload includes requesterMemberStateCode and requesterNumber (the seller's own EU/XI VAT ID). If valid === true returns without requestIdentifier, our state machine routes the order to VERIFIED_MISSING_CONSULTATION_ID until a full consultation record (timestamp + requester/buyer VAT + SHA-256 response digest) is archived.
Scope note: This post describes distributed-systems engineering patterns for VIES API resilience and audit logging; it is not tax or legal advice.
- Engineering deep-dive: EU VIES HTTP 200 Circuit Breaker
- Interactive sandbox: Free 20-Number EU & Northern Ireland VIES Batch Validator
Top comments (0)