EU VAT Number Validation in PHP

A step-by-step guide to validating EU VAT numbers in PHP using the vatnode REST API. Includes examples with curl, Guzzle, and the Laravel HTTP client.

Basic validation with curl

The vatnode API works with PHP's built-in cURL extension — no additional packages required:

PHP — curl
<?php

function validateVatNumber(string $vatId): array
{
    $apiKey = getenv('VATNODE_API_KEY');
    $url    = 'https://api.vatnode.dev/v1/vat/' . urlencode($vatId);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["Authorization: Bearer $apiKey"],
        CURLOPT_TIMEOUT        => 10,
    ]);

    $body       = curl_exec($ch);
    $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($statusCode === 503) {
        return ['error' => 'VIES_UNAVAILABLE'];
    }

    return json_decode($body, true);
}

$result = validateVatNumber('IE6388047V');

if (isset($result['error'])) {
    // Queue for retry
    echo "VIES unavailable, retry later
";
} elseif ($result['valid']) {
    echo "Valid: {$result['companyName']}
"; // GOOGLE IRELAND LIMITED
    echo "CheckId: {$result['checkId']}
";   // store on invoice
} else {
    echo "Invalid VAT number
";
}

Using Guzzle

PHP — Guzzle
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;

$client = new Client([
    'base_uri' => 'https://api.vatnode.dev',
    'headers'  => ['Authorization' => 'Bearer ' . getenv('VATNODE_API_KEY')],
    'timeout'  => 10,
]);

function validateVat(Client $client, string $vatId): array
{
    try {
        $response = $client->get('/v1/vat/' . urlencode($vatId));
        return json_decode($response->getBody()->getContents(), true);
    } catch (ServerException $e) {
        if ($e->getCode() === 503) {
            return ['error' => 'VIES_UNAVAILABLE'];
        }
        throw $e;
    } catch (ClientException $e) {
        return ['error' => 'INVALID_VAT_NUMBER'];
    }
}

$result = validateVat($client, 'DE134214316');

if ($result['valid'] ?? false) {
    // Apply reverse charge
    $vatRate = 0;
} else {
    // Charge local VAT
    $vatRate = $result['countryVat']['standardRate'] / 100;
}

Laravel HTTP client

Laravel's HTTP facade wraps Guzzle with a fluent interface. Add your API key to .env:

PHP — Laravel
<?php
// Add to .env: VATNODE_API_KEY=vat_live_...

use Illuminate\Support\Facades\Http;

function validateVatLaravel(string $vatId): array
{
    $response = Http::withToken(config('services.vatnode.key'))
        ->timeout(10)
        ->get("https://api.vatnode.dev/v1/vat/" . urlencode($vatId));

    if ($response->status() === 503) {
        return ['error' => 'VIES_UNAVAILABLE'];
    }

    $response->throw(); // throws on 4xx errors

    return $response->json();
}

// config/services.php
return [
    'vatnode' => [
        'key' => env('VATNODE_API_KEY'),
    ],
];

Do not block checkout on VIES unavailability. When vatnode returns a 503, queue the validation job (e.g. using Laravel Queue) and proceed with standard VAT. Retry the check asynchronously — VIES will recover.

Add VAT validation to your PHP app

Free plan: 20 requests/month, no credit card. Works with curl, Guzzle, and Laravel.