EU VAT validation in Ruby / Rails
Validate EU VAT numbers in Ruby or Ruby on Rails using the standard Net::HTTP library. Zero gem dependencies — integrates cleanly with ActiveRecord models and Rails service objects.
Ruby's standard Net::HTTP library, combined with the built-in JSON module, is sufficient to call the TaxID REST API without adding gems. The idiomatic Rails pattern is a service object (app/services/vat_validation_service.rb) that wraps the HTTP call, and an ActiveRecord concern or model callback that invokes the service on customer creation or update. This keeps the HTTP logic isolated from the model layer and testable with standard RSpec doubles.
The service class uses Net::HTTP.start with use_ssl: true and sets the Authorization header on the request object. The response body is parsed with JSON.parse, giving a Ruby hash with string keys (valid, status, company_name, company_address, cached, request_id). Symbolise the keys with response.transform_keys(&:to_sym) for idiomatic Ruby access. Wrap the HTTP call in a begin/rescue block catching Net::HTTPError and JSON::ParserError to handle network failures without crashing the calling thread.
For Rails applications, storing the validation result in a VATValidation ActiveRecord model (with columns: customer_id, country_code, vat_number, status, company_name, request_id, validated_at) provides both the audit trail required by EU VAT regulations and a caching layer. Query this model before calling the API: skip the network call if a record exists with validated_at within the last 24 hours for active status, or within the last hour for invalid status.
Implementation steps
- 1
Use Net::HTTP with use_ssl = true for the HTTPS request
Require 'net/http', 'uri', and 'json' at the top of your service class. Parse the URL with URI.parse("https://api.taxid.pro/v1/validate/#{country}/#{vat}") and open a connection with Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 10). Setting explicit timeouts prevents the HTTP call from blocking a Puma worker thread indefinitely when VIES member-state nodes are slow.
- 2
Set the Authorization header with your API key from ENV
Build a Net::HTTP::Get request object: request = Net::HTTP::Get.new(uri). Set the header with request['Authorization'] = "Bearer #{ENV.fetch('TAXID_API_KEY')}". Use ENV.fetch (not ENV[]) so that a missing environment variable raises a KeyError at startup rather than silently sending an unauthenticated request that returns a 401 in production.
- 3
Parse the JSON response with Ruby's built-in JSON module
Call response = http.request(request) and check response.code == '200' before parsing. Parse the body with result = JSON.parse(response.body, symbolize_names: true) to get symbol-keyed access (:status, :company_name, :request_id, etc.). Rescue JSON::ParserError in case the API returns a non-JSON error body, and raise a VATService::ParseError with the raw body included for debugging.
- 4
Update your ActiveRecord model with the validation result and company name
After parsing, upsert a VATValidation record: VATValidation.upsert({ customer_id:, country_code:, vat_number:, status: result[:status], company_name: result[:company_name], request_id: result[:request_id], validated_at: Time.current }, unique_by: [:customer_id, :vat_number]). Also update the parent Customer record's company_name if the VIES-returned name differs, and set tax_exempt: true on the customer when status is 'active'.
Code example
Ruby
require 'net/http'
require 'json'
def validate_vat(country, vat_number)
uri = URI("http://localhost:3000/api/v1/validate/#{country}/#{vat_number}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{ENV['TAXID_API_KEY']}"
response = http.request(request)
JSON.parse(response.body)
end
result = validate_vat('DE', 'DE123456789')
if result['valid']
puts "Valid EU business: #{result['company_name']}"
elsif result['status'] == 'service_unavailable'
# VIES is temporarily down — allow through and validate later
puts 'VIES unavailable — retry later'
else
puts 'Invalid VAT number'
endcURL
curl "http://localhost:3000/api/v1/validate/DE/DE123456789" \
-H "Authorization: Bearer $TAXID_API_KEY"
# {
# "valid": true,
# "status": "active",
# "company_name": "Example GmbH",
# "company_address": "Musterstraße 1, 10115 Berlin",
# "cached": false
# }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...