Vatline

How to validate an EU VAT number in PHP

PHP ships with SoapClient, which is enough to query VIES, the European Commission's VAT validation service. Here is working code and the traps to avoid.

Basic check with SoapClient

<?php
function checkVat(string $raw): array {
    $s = strtoupper(preg_replace('/[\s.\-]/', '', $raw));
    $country = substr($s, 0, 2) === 'GR' ? 'EL' : substr($s, 0, 2);
    $number  = substr($s, 2);

    $client = new SoapClient(
        'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl',
        ['connection_timeout' => 10, 'exceptions' => true]
    );
    try {
        $r = $client->checkVat(['countryCode' => $country, 'vatNumber' => $number]);
    } catch (SoapFault $f) {
        // $f->faultstring: MS_MAX_CONCURRENT_REQ, MS_UNAVAILABLE, INVALID_INPUT...
        return ['error' => $f->faultstring];
    }
    return ['valid' => $r->valid, 'name' => $r->name ?? null, 'address' => $r->address ?? null];
}

Traps

WooCommerce and Laravel

Run the check when the customer enters the number, store the result and the date on the order or customer record, and cache it for 24 hours. In WooCommerce, hook woocommerce_after_checkout_validation; in Laravel, a custom validation rule that reads from cache before calling VIES.

The REST alternative

$ch = curl_init('https://vatline.p.rapidapi.com/v1/check');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json',
                           'X-RapidAPI-Key: ' . $key,
                           'X-RapidAPI-Host: vatline.p.rapidapi.com'],
    CURLOPT_POSTFIELDS => json_encode(['vat_number' => 'FR40303265045']),
]);
$result = json_decode(curl_exec($ch), true);

Flat JSON, cached, with a retryable flag on every error.

Skip the plumbing. Vatline wraps VIES in a REST/JSON API with caching, retry hints and per-country circuit breakers. Free plan: 300 checks a month, no card. Read the docs or try a number now.