VAT Monitoring with Webhooks: Detect Invalid EU Registrations Automatically
When a B2B customer's EU VAT registration lapses mid-subscription, subsequent invoices may carry an unexpected VAT liability. vatnode monitors EU VAT registrations and fires webhooks the moment status changes — so you can act before the next billing cycle.
Why monitoring matters
Validating a customer's VAT number at signup is a good start, but registrations can lapse. A company may deregister from VAT voluntarily, restructure, or simply let their registration expire. If you continue issuing zero-VAT invoices after that happens — relying on a status check that is weeks or months old — you may be applying the reverse charge mechanism without a valid legal basis.
Polling the API manually on a schedule is one option, but it adds operational overhead and still leaves a gap between checks. A better pattern is to subscribe once and receive an event when something changes. That is exactly what vatnode monitoring does.
For the initial validation at checkout or contract creation, see the guide on how to validate EU VAT numbers. Monitoring is the complementary layer for keeping that status current over the lifetime of a subscription. It is available from the Starter plan, which includes up to 25 monitored VAT numbers and 3 webhook endpoints.
Webhook events vatnode sends
When vatnode detects a change during a daily re-validation, it creates an event and delivers it to all active webhook endpoints registered on your account. The four change event types are:
| Event type | When it fires | Recommended action |
|---|---|---|
VAT_BECAME_INVALID | VAT number is deregistered or no longer active in VIES | Pause reverse charge billing; notify finance team |
VAT_BECAME_VALID | Previously invalid number is now active again | Resume reverse charge billing after re-confirming status |
COMPANY_NAME_CHANGED | Legal trading name updated in the EU registry | Update invoice recipient name; flag for customer review |
COMPANY_ADDRESS_CHANGED | Registered address updated in the EU registry | Update invoice recipient address |
Name and address change events are only fired when the VAT number is currently valid. If a registration becomes invalid, only VAT_BECAME_INVALID is sent — not a name or address event for the same check cycle.
How to set up VAT monitoring
Step 1: subscribe to a VAT number
Send a POST request to /v1/subscriptions with the VAT ID you want to monitor. This endpoint requires dashboard session authentication — you would typically call it from your backend when onboarding a new B2B customer.
curl https://api.vatnode.dev/v1/subscriptions \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vat_live_your_key_here" \
-d '{"vatId": "DE134214316"}'Response (201 Created):
{
"id": "sub_01hx9z3k4qw8v2r7n6m5p0y1c",
"vatId": "DE134214316",
"countryCode": "DE",
"status": "active",
"lastCheckedAt": null,
"lastCompanyName": null,
"lastCompanyAddress": null,
"lastValid": null,
"createdAt": "2026-04-23T09:15:00.000Z",
"updatedAt": "2026-04-23T09:15:00.000Z"
}The first daily check will populate lastValid, lastCompanyName, and lastCompanyAddress. No event is fired on the first check — vatnode needs a baseline to compare against before it can detect a change.
Step 2: register a webhook endpoint
Webhook endpoints must use HTTPS. vatnode will reject plain HTTP URLs. Send a POST to /v1/webhooks with your endpoint URL.
curl https://api.vatnode.dev/v1/webhooks \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vat_live_your_key_here" \
-d '{"url": "https://app.example.com/webhooks/vatnode"}'Response (201 Created):
{
"id": "wh_01hx9z8m2nq4v6p3k7r0y5w9c",
"url": "https://app.example.com/webhooks/vatnode",
"status": "active",
"secret": "whsec_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
"createdAt": "2026-04-23T09:16:00.000Z",
"updatedAt": "2026-04-23T09:16:00.000Z"
}Save the secret immediately. The secret value is only returned once at creation time and is never shown again. Store it in your environment variables. You will use it to verify the signature on every incoming webhook request.
Step 3: handle the webhook event in your server
vatnode signs every delivery with an X-vatnode-Signature header. The value is t=<unix_timestamp>,v1=<hmac_sha256>. The HMAC is computed over timestamp.payload using a signing key derived from your webhook secret and webhook ID. Always verify the signature before acting on the event.
Below is a complete Node.js / Express handler that verifies the signature and handles the VAT_BECAME_INVALID event:
import express from 'express'
import { createHmac } from 'crypto'
const app = express()
// Use raw body middleware for this route — signature verification requires
// the exact bytes that were sent, before any JSON parsing.
app.post(
'/webhooks/vatnode',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signatureHeader = req.headers['x-vatnode-signature'] as string | undefined
if (!signatureHeader) {
return res.status(401).json({ error: 'Missing signature header' })
}
// Parse t=<timestamp>,v1=<signature>
const parts = Object.fromEntries(
signatureHeader.split(',').map((part) => part.split('=') as [string, string])
)
const timestamp = parts['t']
const receivedSig = parts['v1']
if (!timestamp || !receivedSig) {
return res.status(401).json({ error: 'Malformed signature header' })
}
// Reject events older than 5 minutes to prevent replay attacks
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)
if (age > 300) {
return res.status(401).json({ error: 'Webhook timestamp too old' })
}
// Verify HMAC-SHA256 signature
const webhookSecret = process.env.VATNODE_WEBHOOK_SECRET! // whsec_... from creation
const payloadStr = req.body.toString('utf8')
const expectedSig = createHmac('sha256', webhookSecret)
.update(`${timestamp}.${payloadStr}`)
.digest('hex')
if (expectedSig !== receivedSig) {
return res.status(401).json({ error: 'Invalid signature' })
}
// Signature verified — safe to parse and process
const event = JSON.parse(payloadStr) as {
event: string
data: {
vatId: string
countryCode: string
subscriptionId: string
previousState?: string
newState?: string
previousName?: string
newName?: string
previousAddress?: string
newAddress?: string
}
timestamp: string
}
// Always respond 200 before doing any async work
res.status(200).json({ received: true })
// Handle event types
switch (event.event) {
case 'VAT_BECAME_INVALID': {
const { vatId, countryCode } = event.data
console.log(`VAT number ${vatId} (${countryCode}) is no longer valid`)
// Pause reverse charge billing for this customer
await billingService.pauseReverseCharge(vatId)
// Alert the finance team
await notificationService.notify({
channel: 'finance-alerts',
message: `Customer VAT number ${vatId} has become invalid. Billing paused pending review.`,
})
break
}
case 'VAT_BECAME_VALID': {
const { vatId } = event.data
console.log(`VAT number ${vatId} is now valid again`)
// Resume reverse charge billing after re-confirming with your team
await billingService.resumeReverseCharge(vatId)
break
}
case 'COMPANY_NAME_CHANGED': {
const { vatId, newName } = event.data
console.log(`Company name for ${vatId} changed to: ${newName}`)
await customerService.updateCompanyName(vatId, newName ?? null)
break
}
case 'COMPANY_ADDRESS_CHANGED': {
const { vatId, newAddress } = event.data
console.log(`Company address for ${vatId} changed to: ${newAddress}`)
await customerService.updateCompanyAddress(vatId, newAddress ?? null)
break
}
default:
console.log(`Unhandled vatnode event: ${event.event}`)
}
}
)
app.listen(3000)The webhook payload shape for a VAT_BECAME_INVALID event:
{
"event": "VAT_BECAME_INVALID",
"data": {
"vatId": "DE134214316",
"countryCode": "DE",
"subscriptionId": "sub_01hx9z3k4qw8v2r7n6m5p0y1c",
"previousState": "valid",
"newState": "invalid"
},
"timestamp": "2026-04-23T07:04:12.381Z"
}Step 4: take action on the event
The right action depends on your billing model. For subscription businesses, the most common response to VAT_BECAME_INVALID is to:
- Stop applying the reverse charge mechanism on the next invoice — charge VAT at the applicable rate instead.
- Notify the customer so they can update their VAT details or confirm their registration status with their tax authority.
- Alert your finance or accounts team so they can review open invoices if needed.
For COMPANY_NAME_CHANGED and COMPANY_ADDRESS_CHANGED, the recommended action is to update the invoice recipient details in your billing system and flag the customer record for review — the legal entity may have been restructured.
Replacing a direct VIES integration? The vatnode REST API handles VIES querying and national registry fallback without requiring any SOAP libraries in your codebase. See the VIES REST API alternative guide for a side-by-side comparison and migration steps.
Frequently asked questions
How often does vatnode check monitored VAT numbers?
vatnode re-validates all active monitoring subscriptions daily. Each check goes through VIES and, where available, national registry fallback via local tax authority and company registry APIs. Changes detected during a check trigger webhook events immediately — you do not need to wait for a polling interval.
What happens if the webhook delivery fails?
vatnode retries failed deliveries with exponential backoff — up to 5 attempts. The retry schedule is approximately 1 s, 2 s, 4 s, 8 s, and 16 s after the previous attempt. Every delivery attempt (success or failure) is logged and visible in the dashboard under your webhook's delivery history. HTTP 4xx responses (except 429) are treated as permanent failures and are not retried.
Which EU countries are covered by monitoring?
All 27 EU member states are checked via VIES on every monitoring run. National registry fallback adds deeper coverage for a subset of countries where tax authority and company registry APIs are available — this is used automatically when a VIES country node is temporarily unavailable, so monitoring continuity is maintained even during partial VIES outages.
Next steps
- Validate EU VAT numbers at checkout — the first-check guide that pairs with monitoring.
- Quickstart guide — make your first API call in under 5 minutes.
- View plan limits — monitoring subscriptions and webhook endpoints are available from the Starter plan.
Ready to monitor EU VAT registrations automatically?
Starter plan includes 25 monitored VAT numbers and 3 webhook endpoints — no credit card required to get started.