Why Automate Reverse Charge Notes?
When a business sells digital services across EU borders (or to GB clients post-Brexit), VAT legislation often requires the buyer to self-account for VAT rather than the seller charging it. Manually adding the correct legal text to every invoice is tedious and error-prone, especially when the wording differs between EU and UK regulations. Automating this inside WHMCS eliminates human error, keeps every invoice audit-ready, and saves significant administrative time.
Pre-Requirements Checklist
Before deploying any code, make sure the following items are in place:
- VAT custom field -- WHMCS must have a client-level custom field (or the native
tax_id/vatfield) that stores each client's VAT registration number. - Accurate client data -- Every client record should include the correct country code and the
taxexemptflag set appropriately for B2B customers. - Currency mapping -- The
tblcurrenciestable must contain valid currency codes so the note template can reference the right symbol. - Company home country -- The WHMCS setting
TaxEUHomeCountrymust reflect your own country code; the hook uses it to skip domestic invoices.
Verifying Prerequisites with SQL
Run the queries below directly against the WHMCS database to confirm that the required data exists.
Check the VAT Custom Field
SELECT id, fieldname, description FROM tblcustomfields WHERE fieldname LIKE '%VAT%';
Note the id value -- you will need it for the hook configuration.
Check Client Information
SELECT id, firstname, lastname, country, taxexempt, currency FROM tblclients WHERE id = 1;
Verify that country is a two-letter ISO code and taxexempt is set for B2B clients.
Check the VAT Value Stored for a Client
SELECT value FROM tblcustomfieldsvalues WHERE fieldid = 10 AND relid = 1;
Replace fieldid and relid with the actual IDs from your installation.
Adding a VAT Field (If Missing)
If the SQL query above returns no rows, create the field through the WHMCS admin panel:
- Navigate to Setup > Custom Client Fields.
- Click Add New Custom Field.
- Set the field name to something like
VAT Numberand the field type to Text Box. - Save and note the new field ID for use in the hook.
Pre-Requirements Verification Script
The following PHP script uses the WHMCS Capsule ORM to programmatically verify that all prerequisites are met before you deploy the hook.
<?php
/**
* Pre-requirements verification for the reverse charge VAT hook.
* Run from the WHMCS root directory: php verify_vat_prereqs.php
*/
require_once __DIR__ . '/init.php';
use WHMCS\Database\Capsule;
// 1. Check TaxEUHomeCountry
$homeCountry = Capsule::table('tblconfiguration')
->where('setting', 'TaxEUHomeCountry')
->value('value');
echo $homeCountry
? "Home country: $homeCountry\n"
: "ERROR: TaxEUHomeCountry is not set.\n";
// 2. Check VAT custom field exists
$vatField = Capsule::table('tblcustomfields')
->where('fieldname', 'LIKE', '%VAT%')
->first(['id', 'fieldname']);
if ($vatField) {
echo "VAT field found: ID {$vatField->id} ({$vatField->fieldname})\n";
} else {
echo "ERROR: No custom field matching 'VAT' found.\n";
}
// 3. Check at least one client has a VAT value
if ($vatField) {
$sample = Capsule::table('tblcustomfieldsvalues')
->where('fieldid', $vatField->id)
->whereNotNull('value')
->where('value', '!=', '')
->first(['relid', 'value']);
echo $sample
? "Sample VAT value: Client {$sample->relid} => {$sample->value}\n"
: "WARNING: No clients have a VAT number stored yet.\n";
}
// 4. Check currencies
$currencies = Capsule::table('tblcurrencies')->pluck('code')->toArray();
echo "Currencies configured: " . implode(', ', $currencies) . "\n";
The WHMCS Hook: add_reverse_vat_tax_to_invoices.php
Save this file in includes/hooks/ inside your WHMCS installation. It fires on every new invoice and appends the correct reverse charge text when the client qualifies.
<?php
/**
* Private Devops LTD - WHMCS VAT Reverse Charge Hook
* ---------------------------------------------------
* This hook automatically applies reverse charge notes to invoices
* for eligible EU and GB clients based on VAT regulations at the time
* of invoice creation.
*
* Author: Private Devops LTD
* Website: https://privatedevops.com
*
* Description:
* ------------
* This hook ensures that invoices created in WHMCS comply with VAT regulations for
* B2B and B2C transactions across the EU and the UK. It dynamically applies reverse
* charge notes or VAT charges to invoices based on the client's country, tax exemption
* status, and VAT registration. For eligible B2B transactions, no VAT is charged,
* and the responsibility for VAT reporting shifts to the client (reverse charge).
*
* Key Features:
* -------------
* - Automatically triggered during invoice creation in WHMCS.
* - Adds reverse charge notes for EU and GB clients with valid VAT numbers.
* - Ensures VAT compliance for both B2B (reverse charge) and B2C (standard VAT) scenarios.
* - Fetches company country code dynamically from WHMCS settings (`TaxEUHomeCountry`).
* - Flexible configuration for VAT custom field ID and note templates.
* - Detailed logging for debugging and activity tracking in WHMCS.
*
* How to Use:
* -----------
* 1. Save this script in the `includes/hooks/` directory of your WHMCS installation.
* 2. Update the `$customFieldId` variable to match the VAT custom field ID in your WHMCS:
* - Run: `SELECT id, fieldname FROM tblcustomfields WHERE fieldname LIKE '%VAT%';`
* - Replace the default value of `$customFieldId` with the retrieved ID.
* 3. Customize the `$noteTemplateEU` and `$noteTemplateGB` variables for EU and GB notes.
* 4. Test the hook by creating new invoices for clients from EU countries or GB.
* 5. Monitor the WHMCS Activity Log (Utilities > Logs > Activity Log) for hook execution details.
*
* Requirements:
* -------------
* - WHMCS must be configured with a VAT custom field or the native `vat` field.
* - Clients must:
* - Have a valid VAT number (stored in the configured custom field or native field).
* - Be marked as tax-exempt (for reverse charge eligibility).
* - Reside in an EU country or GB for processing.
* - Ensure your company country code is correctly set in WHMCS (`TaxEUHomeCountry`).
*
* Key Regulations Referenced:
* ----------------------------
* - **EU VAT Directive 2006/112/EC (Article 196)**:
* - Governs reverse charge mechanisms for B2B cross-border services within the EU.
* - **UK VAT Act 1994**:
* - Governs reverse charge rules for services supplied to UK businesses post-Brexit.
* - **Local VAT Rules (e.g., Bulgaria)**:
* - Applied for B2C transactions where no valid VAT number is provided.
*
* Notes:
* ------
* - This hook applies only to invoices created after its implementation.
* - Skips clients without valid VAT numbers or tax exemption status.
* - Provides activity log entries for debugging and tracking.
* - Ideal for maintaining compliance with VAT regulations in real time.
*/
use WHMCS\Database\Capsule;
// Configuration: Update this with your VAT custom field ID
$customFieldId = 10; // Change this to match your VAT custom field ID
// Array of EU country codes (including GB for B2B logic)
$euCountries = [
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR',
'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI',
'SK', 'GB'
];
// Configuration: Note templates for EU and GB
$noteTemplateEU = "VAT (0%): 0.00 {currency}\nDomestic turnover is not taxable, your VAT registration number is: {vat}.\nThis transaction complies with Article 196 of the EU VAT Directive 2006/112/EC (Reverse Charge).";
$noteTemplateGB = "No VAT charged under UK reverse charge rules. Your VAT registration number is: {vat}.\nThis transaction complies with the UK VAT Act 1994 and post-Brexit domestic reverse charge regulations.";
add_hook('InvoiceCreation', 1, function ($vars) use ($customFieldId, $euCountries, $noteTemplateEU, $noteTemplateGB) {
$invoiceId = $vars['invoiceid'];
// Fetch company country code from WHMCS TaxEUHomeCountry setting
$companyCountryCode = Capsule::table('tblconfiguration')
->where('setting', 'TaxEUHomeCountry')
->value('value');
if (!$companyCountryCode) {
logActivity("VAT Hook Debug: Failed to retrieve company country code from TaxEUHomeCountry.");
return;
}
// Fetch invoice details to get the user ID
$invoice = Capsule::table('tblinvoices')
->where('id', $invoiceId)
->first(['userid']);
if (!$invoice) {
logActivity("VAT Hook Debug: No invoice found for Invoice ID: $invoiceId");
return;
}
$userId = $invoice->userid;
// Fetch client details
$clientDetails = Capsule::table('tblclients')
->where('id', $userId)
->first(['country', 'taxexempt', 'currency']);
if (!$clientDetails) {
logActivity("VAT Hook Debug: No client found for User ID: $userId");
return;
}
$clientCountry = $clientDetails->country;
// **EARLY CHECK**: Skip clients not in the EU or GB immediately
if (!in_array($clientCountry, $euCountries)) {
logActivity("VAT Hook Debug: Skipped User ID: $userId (Country: $clientCountry) - Not in EU or GB.");
return;
}
// Retrieve VAT number now that we've confirmed EU/GB
$vatNumber = Capsule::table('tblcustomfieldsvalues')
->where('fieldid', $customFieldId)
->where('relid', $userId)
->value('value');
/* if (empty($vatNumber)) {
$vatNumber = Capsule::table('tblclients')
->where('id', $userId)
->value('vat');
}
*/
if (!$vatNumber) {
logActivity("VAT Hook Debug: No VAT Number found for User ID: $userId.");
return;
}
$isTaxExempt = $clientDetails->taxexempt;
$currencyId = $clientDetails->currency;
// Fetch the client currency
$currency = Capsule::table('tblcurrencies')
->where('id', $currencyId)
->value('code');
// Log variables for debugging
logActivity("VAT Hook Debug: Invoice ID: $invoiceId, User ID: $userId, Country: $clientCountry, Company Country: $companyCountryCode, Tax Exempt: $isTaxExempt, VAT Number: $vatNumber");
// Check conditions for adding the reverse charge note
if (
in_array($clientCountry, $euCountries) &&
$clientCountry !== $companyCountryCode &&
!empty($isTaxExempt) &&
!empty($vatNumber)
) {
// Use GB-specific note for UK clients
$note = ($clientCountry === 'GB')
? str_replace('{vat}', $vatNumber, $noteTemplateGB)
: str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateEU);
// Update the invoice with the note
Capsule::table('tblinvoices')
->where('id', $invoiceId)
->update(['notes' => $note]);
logActivity("VAT Hook Debug: Added reverse charge note to Invoice ID: $invoiceId for User ID: $userId");
} else {
logActivity("VAT Hook Debug: Conditions not met for Invoice ID: $invoiceId. Country: $clientCountry, Tax Exempt: $isTaxExempt, VAT: $vatNumber");
}
});
Bulk-Updating Existing Invoices: update_old_invoices.php
If you already have invoices that should carry reverse charge notes, the script below iterates through all paid and unpaid invoices and applies the same logic retroactively. Run it once from the WHMCS root directory.
<?php
/**
* Private Devops LTD - WHMCS VAT Reverse Charge Hook
* ---------------------------------------------------
* This hook automatically applies reverse charge notes to invoices
* for eligible EU and GB clients based on VAT regulations at the time
* of invoice creation.
*
* Author: Private Devops LTD
* Website: https://privatedevops.com
*
* Description:
* ------------
* This hook ensures that invoices created in WHMCS comply with VAT regulations for
* B2B and B2C transactions across the EU and the UK. It dynamically applies reverse
* charge notes or VAT charges to invoices based on the client's country, tax exemption
* status, and VAT registration. For eligible B2B transactions, no VAT is charged,
* and the responsibility for VAT reporting shifts to the client (reverse charge).
*
* Key Features:
* -------------
* - Automatically triggered during invoice creation in WHMCS.
* - Adds reverse charge notes for EU and GB clients with valid VAT numbers.
* - Ensures VAT compliance for both B2B (reverse charge) and B2C (standard VAT) scenarios.
* - Fetches company country code dynamically from WHMCS settings (`TaxEUHomeCountry`).
* - Flexible configuration for VAT custom field ID and note templates.
* - Detailed logging for debugging and activity tracking in WHMCS.
*
* How to Use:
* -----------
* 1. Save this script in the `includes/hooks/` directory of your WHMCS installation.
* 2. Update the `$customFieldId` variable to match the VAT custom field ID in your WHMCS:
* - Run: `SELECT id, fieldname FROM tblcustomfields WHERE fieldname LIKE '%VAT%';`
* - Replace the default value of `$customFieldId` with the retrieved ID.
* 3. Customize the `$noteTemplateEU` and `$noteTemplateGB` variables for EU and GB notes.
* 4. Test the hook by creating new invoices for clients from EU countries or GB.
* 5. Monitor the WHMCS Activity Log (Utilities > Logs > Activity Log) for hook execution details.
*
* Requirements:
* -------------
* - WHMCS must be configured with a VAT custom field or the native `vat` field.
* - Clients must:
* - Have a valid VAT number (stored in the configured custom field or native field).
* - Be marked as tax-exempt (for reverse charge eligibility).
* - Reside in an EU country or GB for processing.
* - Ensure your company country code is correctly set in WHMCS (`TaxEUHomeCountry`).
*
* Key Regulations Referenced:
* ----------------------------
* - **EU VAT Directive 2006/112/EC (Article 196)**:
* - Governs reverse charge mechanisms for B2B cross-border services within the EU.
* - **UK VAT Act 1994**:
* - Governs reverse charge rules for services supplied to UK businesses post-Brexit.
* - **Local VAT Rules (e.g., Bulgaria)**:
* - Applied for B2C transactions where no valid VAT number is provided.
*
* Notes:
* ------
* - This hook applies only to invoices created after its implementation.
* - Skips clients without valid VAT numbers or tax exemption status.
* - Provides activity log entries for debugging and tracking.
* - Ideal for maintaining compliance with VAT regulations in real time.
*/
require_once __DIR__ . '/init.php';
use WHMCS\Database\Capsule;
// Configuration
$customFieldId = 10; // Change this to match your VAT custom field ID
// Configuration: Note templates for EU and GB
$noteTemplateEU = "VAT (0%): 0.00 {currency}\nDomestic turnover is not taxable, your VAT registration number is: {vat}.\nThis transaction complies with Article 196 of the EU VAT Directive 2006/112/EC (Reverse Charge). Under this mechanism, the buyer is responsible for reporting VAT in their respective EU country.";
$noteTemplateGB = "No VAT charged under UK reverse charge rules. Your VAT registration number is: {vat}.\n This transaction complies with the UK VAT Act 1994 and post-Brexit domestic reverse charge regulations. Under these rules, the buyer is required to self-account for VAT.";
// Fetch company country code from WHMCS settings
$companyCountryCode = Capsule::table('tblconfiguration')
->where('setting', 'TaxEUHomeCountry')
->value('value');
if (!$companyCountryCode) {
echo "Error: Failed to retrieve company country code from TaxEUHomeCountry.\n";
exit;
}
// Array of EU country codes (including GB for B2B rules)
$euCountries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB'];
// Ensure $euCountries is an array
if (!is_array($euCountries) || empty($euCountries)) {
echo "Error: Invalid EU country list.\n";
exit;
}
try {
echo "Starting invoice updates...\n";
$invoices = Capsule::table('tblinvoices')
->whereIn('status', ['Unpaid', 'Paid'])
->get();
foreach ($invoices as $invoice) {
$invoiceId = $invoice->id;
$userId = $invoice->userid;
// Fetch client details
$clientDetails = Capsule::table('tblclients')
->where('id', $userId)
->first(['country', 'taxexempt', 'currency']);
if (!$clientDetails) {
echo "No client found for User ID: $userId. Skipping invoice ID: $invoiceId.\n";
continue;
}
$clientCountry = $clientDetails->country;
$isTaxExempt = $clientDetails->taxexempt;
$currencyId = $clientDetails->currency;
// **EARLY CHECK**: If client is not in EU or GB, skip now
if (!in_array($clientCountry, $euCountries)) {
echo "User ID: $userId (Country: $clientCountry) not in EU/GB. Skipping invoice ID: $invoiceId.\n";
continue;
}
// Retrieve VAT number from custom field only if in EU/GB
$vatNumber = Capsule::table('tblcustomfieldsvalues')
->where('fieldid', $customFieldId)
->where('relid', $userId)
->value('value');
/* Check disabled because return errors with clients without vat field */
/*
if (empty($vatNumber)) {
$vatNumber = Capsule::table('tblclients')
->where('id', $userId)
->value('vat');
}
*/
// Fetch the currency code
$currency = Capsule::table('tblcurrencies')
->where('id', $currencyId)
->value('code');
// Log details for debugging
echo "Checking invoice ID: $invoiceId, User ID: $userId, Country: $clientCountry, Tax Exempt: $isTaxExempt, VAT Number: $vatNumber, Currency: $currency\n";
// Process conditions after confirming EU/GB membership
if (
$clientCountry !== $companyCountryCode &&
!empty($isTaxExempt) &&
!empty($vatNumber)
) {
// Use different note templates for EU and GB
$note = ($clientCountry === 'GB')
? str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateGB)
: str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateEU);
// Update the invoice notes
Capsule::table('tblinvoices')
->where('id', $invoiceId)
->update(['notes' => $note]);
echo "Updated invoice ID: $invoiceId with reverse charge note.\n";
} else {
echo "Conditions not met for invoice ID: $invoiceId. Skipping.\n";
}
}
echo "Invoice updates completed.\n";
} catch (\Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Source Code
Both scripts are available on GitHub: privatedevops/scripts/WHMCS.
Need help with this?
Our team handles this kind of work daily. Let us take care of your infrastructure.
Related Articles
The Ultimate Guide to Linux Server Management in 2025
A comprehensive guide to modern Linux server management covering automation, containerization, cloud integration, AI-driven operations, security best practices, and essential tooling for 2025.
Server & DevOpsFixing "421 Misdirected Request" for Plesk Sites on Ubuntu 22.04 After Apache Update
Resolve the 421 Misdirected Request error affecting all HTTPS sites on Plesk for Ubuntu 22.04 after an Apache update, caused by changed SNI requirements in the nginx-to-Apache proxy chain.
Server & DevOpsHow to Set Up GlusterFS on Ubuntu
A complete guide to setting up a distributed, replicated GlusterFS filesystem across multiple Ubuntu 22.04 nodes, including installation, volume creation, client mounting, maintenance, and troubleshooting.