This guide covers EU VAT number validation in PHP for two major frameworks: Laravel and Symfony. Both integrate the TaxID API using an HTTP client (Guzzle or Symfony HttpClient), but the wiring into the framework's service container and the patterns for using it in controllers differ. If you are using plain PHP without a framework, the standalone cURL function is the right starting point.
Standalone PHP (No Framework)
<?php
function validateVat(string $vatNumber): array
{
$country = strtoupper(substr(str_replace(' ', '', $vatNumber), 0, 2));
$vat = strtoupper(str_replace(' ', '', $vatNumber));
$apiKey = getenv('TAXID_API_KEY');
$ch = curl_init("https://taxid.dev/api/v1/validate/{$country}/{$vat}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$apiKey}"],
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
]);
$body = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return ['valid' => false, 'status' => 'service_unavailable', 'company_name' => null];
}
return json_decode($body, true);
}Laravel: Service Class and Provider
<?php
namespace App\Services;
use Illuminate\Http\Client\Factory as Http;
use Illuminate\Support\Facades\Log;
class VatValidator
{
public function __construct(private Http $http) {}
public function validate(string $vatNumber): array
{
$normalised = strtoupper(str_replace(' ', '', $vatNumber));
$country = substr($normalised, 0, 2);
try {
$response = $this->http
->withToken(config('services.taxid.key'))
->timeout(5)
->get("https://taxid.dev/api/v1/validate/{$country}/{$normalised}");
return $response->json();
} catch (\Exception $e) {
Log::warning('TaxID API unavailable', ['error' => $e->getMessage()]);
return [
'valid' => false,
'status' => 'service_unavailable',
'company_name' => null,
];
}
}
public function isValid(string $vatNumber): bool
{
return $this->validate($vatNumber)['status'] === 'active';
}
}<?php
namespace App\Providers;
use App\Services\VatValidator;
use Illuminate\Support\ServiceProvider;
class VatServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(VatValidator::class, fn($app) =>
new VatValidator($app->make('Illuminate\Http\Client\Factory'))
);
}
}
// Add to config/services.php:
// 'taxid' => ['key' => env('TAXID_API_KEY')],
// Register in config/app.php providers array:
// App\Providers\VatServiceProvider::class,Laravel Controller Usage
<?php
namespace App\Http\Controllers;
use App\Services\VatValidator;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class CheckoutController extends Controller
{
public function __construct(private VatValidator $vatValidator) {}
public function validateVat(Request $request): JsonResponse
{
$request->validate(['vat_number' => 'required|string|min:8|max:20']);
$result = $this->vatValidator->validate($request->input('vat_number'));
return match ($result['status']) {
'active' => response()->json(['valid' => true, 'company' => $result['company_name']]),
'inactive' => response()->json(['valid' => false, 'error' => 'vat_inactive'], 422),
'format_invalid' => response()->json(['valid' => false, 'error' => 'bad_format'], 422),
default => response()->json(['valid' => false, 'unavailable' => true]),
};
}
}Symfony: Dependency-Injected Service
<?php
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Psr\Log\LoggerInterface;
class VatValidatorService
{
public function __construct(
private HttpClientInterface $httpClient,
private LoggerInterface $logger,
private string $apiKey,
) {}
public function validate(string $vatNumber): array
{
$normalised = strtoupper(str_replace(' ', '', $vatNumber));
$country = substr($normalised, 0, 2);
try {
$response = $this->httpClient->request(
'GET',
"https://taxid.dev/api/v1/validate/{$country}/{$normalised}",
['headers' => ['Authorization' => "Bearer {$this->apiKey}"], 'timeout' => 5]
);
return $response->toArray();
} catch (\Exception $e) {
$this->logger->warning('TaxID unavailable: ' . $e->getMessage());
return ['valid' => false, 'status' => 'service_unavailable', 'company_name' => null];
}
}
}# Symfony DI configuration
services:
App\Service\VatValidatorService:
arguments:
$apiKey: '%env(TAXID_API_KEY)%'PHPUnit Tests
<?php
use App\Services\VatValidator;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\Response;
use PHPUnit\Framework\TestCase;
class VatValidatorTest extends TestCase
{
private function makeValidator(array $responseData): VatValidator
{
$mockResponse = $this->createMock(Response::class);
$mockResponse->method('json')->willReturn($responseData);
$mockHttp = $this->createMock(Factory::class);
$mockHttp->method('withToken')->willReturnSelf();
$mockHttp->method('timeout')->willReturnSelf();
$mockHttp->method('get')->willReturn($mockResponse);
return new VatValidator($mockHttp);
}
public function test_returns_valid_for_active_vat(): void
{
$validator = $this->makeValidator([
'valid' => true, 'status' => 'active',
'company_name' => 'Test GmbH', 'request_id' => 'r1',
]);
$result = $validator->validate('DE123456789');
$this->assertTrue($result['valid']);
$this->assertEquals('active', $result['status']);
}
public function test_returns_invalid_for_inactive_vat(): void
{
$validator = $this->makeValidator(['valid' => false, 'status' => 'inactive']);
$this->assertFalse($validator->isValid('DE000000000'));
}
}Related guides
Start validating EU VAT numbers
Free plan — 100 validations/month. No credit card required.