EU VAT validation in PHP / Laravel
Validate EU VAT numbers in PHP applications with Laravel, Symfony, or plain PHP. Includes service class, validation rule, and database storage pattern.
PHP applications using Laravel can integrate TaxID validation through a combination of a service class, a custom FormRequest validation rule, and a database migration for storing results. The service class wraps the HTTP call, the custom rule hooks into Laravel's validator so VAT numbers can be validated in any FormRequest with a single line, and the database table provides the audit trail required by EU VAT regulations.
The HTTP call itself uses Laravel's Http facade (built on Guzzle): Http::withToken(config('services.taxid.api_key'))->get("https://api.taxid.pro/v1/validate/{$country}/{$vat}"). The response JSON maps to a PHP array with keys valid, status, company_name, company_address, cached, and request_id. Cache the response in Redis using Cache::put("vat:{$country}:{$vat}", $response, $ttl) where $ttl is 86400 for active numbers and 3600 for invalid ones.
Symfony users follow the same pattern using Symfony's HttpClient component (HttpClient::create()->request('GET', $url, ['auth_bearer' => $apiKey])) and a custom ConstraintValidator. The key architectural difference is registering the validator as a tagged service in services.yaml. In either framework, surfacing validation feedback in the Blade or Twig template as an inline message (company name on success, error text on failure) significantly reduces checkout abandonment compared to generic error messages.
Implementation steps
- 1
Create VATValidationService
Generate a service class (php artisan make:service VATValidationService) that injects Laravel's Http facade and calls GET https://api.taxid.pro/v1/validate/{country}/{vat} with withToken(config('services.taxid.api_key')). Return the decoded JSON as a PHP object or DTO so callers never access raw array keys and get IDE autocompletion on status, company_name, and request_id.
- 2
Add Laravel validation rule
Create a custom rule class (php artisan make:rule ValidEUVatNumber) that calls VATValidationService::validate() in the passes() method and returns true only when status === 'active'. Use the rule in your FormRequest: 'vat_number' => ['required', 'string', new ValidEUVatNumber($request->country)]. This makes VAT validation available anywhere Laravel's validator runs, including API controllers and queue jobs.
- 3
Cache results in Redis
Before making an HTTP request in VATValidationService, check Cache::get("vat:{$country}:{$vat}"). On a cache miss, call the API and store the result with Cache::put("vat:{$country}:{$vat}", $result, $ttl) where $ttl = $result->status === 'active' ? 86400 : 3600. Using Laravel's cache abstraction means you can switch from Redis to Memcached or database cache without changing the service class.
- 4
Display validation feedback in Blade
After the form submits, pass the VATValidationResult to your Blade view and conditionally render an inline confirmation block: show the company_name in green when status is active, an error alert when invalid, and a warning notice when service_unavailable. Displaying the company name that VIES returned is a strong trust signal for B2B buyers and reduces support tickets from customers unsure whether their number was accepted.
Code example
Node.js
const res = await fetch(
'http://localhost:3000/api/v1/validate/DE/DE123456789',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { valid, status, company_name, company_address } = await res.json();
if (valid) {
console.log(`Valid EU business: ${company_name}`);
} else if (status === 'service_unavailable') {
// VIES is temporarily down — retry or allow with manual check
console.log('VIES unavailable — check back in a few minutes');
} else {
console.log('Invalid VAT number — charge local tax rate');
}Python
import requests
res = requests.get(
"http://localhost:3000/api/v1/validate/DE/DE123456789",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = res.json()
if data["valid"]:
print(f"Valid: {data['company_name']}")
elif data["status"] == "service_unavailable":
print("VIES temporarily unavailable")
else:
print("Invalid VAT number")API response
The TaxID API returns a consistent JSON response for every validation request:
{
"valid": true,
"status": "active",
"country_code": "DE",
"vat_number": "123456789",
"company_name": "Example GmbH",
"company_address": "Musterstraße 1, 10115 Berlin",
"request_date": "2026-05-10T00:00:00.000Z",
"cached": false,
"request_id": "req_01j..."
}Error handling
The API uses a consistent Stripe-style error format. Always handle service_unavailable separately — VIES has occasional downtime and you should not reject valid customers during outages.
activeVAT number is valid and the business is registered
invalidVAT number format is wrong or not registered in VIES
service_unavailableVIES or the national system is temporarily down — retry later
Further reading
EU VAT Number Validation: The Complete Developer Guide (2026)
VIES is SOAP-based, unreliable, and has no caching. This guide explains how EU VAT validation works end-to-end, how to h…
VAT for Developers: The Complete 2026 Implementation Guide
VAT is a consumption tax collected at each stage of the supply chain. For developers, this means implementing rate looku…
Evaluating EU VAT APIs? Compare TaxID with:
Related use cases
Stripe EU VAT: Validate Tax IDs Before Charging Customers
Stripe EU VAT integration guide: validate EU VAT numbers server-side before applying zero-rate B2B e...
UK VAT validation in Shopify B2B
Validate UK VAT numbers for B2B customers in Shopify. Required under UK Making Tax Digital rules for...
WooCommerce Spain NIF/CIF validation
Validate Spanish NIF and CIF numbers in WooCommerce checkout. Automatically apply B2B tax exemptions...