Skip to content
Shahid Malla

WHMCS Google Tag Manager and GA4: Hook File, Purchase Event, Consent

Google Tag Manager and GA4 in WHMCS without template edits: the output hooks, a purchase event built from the order, consent mode, keeping staff and personal data out, and testing with Tag Assistant.

· Published · Updated · 8 min read
On this page (9 sections)

To add Google Tag Manager and GA4 to WHMCS, put one hook file in includes/hooks/. ClientAreaHeadOutput prints the container script in the head of every client area page, ClientAreaHeaderOutput prints the noscript frame at the top of the body, and ShoppingCartCheckoutCompletePage pushes a GA4 purchase event to the data layer. GA4 itself is configured as tags inside GTM.

Should you use the WHMCS Google Analytics addon or Google Tag Manager?

Use the addon if GA4 is the only tag you need, and GTM if you run several tags or want control of the data. WHMCS ships a Google Analytics addon module that supports GA4. You activate it at Configuration > System Settings > Addon Modules and enter your Measurement ID.

WHMCS Google Analytics addonGTM through a hook file
SetupActivate and enter a Measurement IDOne PHP file plus tags in GTM
Other tags (Ads, chat, consent banner)NoYes, all managed in GTM
ConversionsGoal tracking on cart.php?a=completeA purchase event you define, with items
Offline gatewaysThe documentation says conversions cannot be tracked for Bank Transfer or Mail In PaymentSent with invoice_paid false when the complete page loads

Never run both for the same Measurement ID, or page views can be counted twice. Remove the addon, or any gtag.js snippet pasted into a template, before you publish the container.

Which WHMCS hooks print each part of the GTM snippet?

Two output hooks place the container code, and one cart hook handles the purchase. Google's install instructions ask for the script as high in the head as possible and the noscript block immediately after the opening body tag. The output hooks reference says where each hook's HTML lands.

HookWhere WHMCS puts the outputUse it for
ClientAreaHeadOutputWithin the head tagsThe GTM container script
ClientAreaHeaderOutputAt the top of the bodyThe GTM noscript iframe
ClientAreaFooterOutputBefore the closing body tagNothing. GTM does not need it.
ShoppingCartCheckoutCompletePageThe order complete pageThe purchase push

A hook file works whatever theme is active, which a template edit does not. The one condition is that the theme keeps the output variables of the stock header and footer, as the theme documentation recommends. The addon page names one of them: {$footeroutput} must sit immediately before </body>. My WHMCS theme structure guide explains child themes.

How do you write the ClientAreaHeadOutput hook for the GTM container?

Create includes/hooks/gtm.php with the two output hooks and a check that skips staff. Replace GTM-XXXXXXX with your container ID.

<?php
if (!defined('WHMCS')) {
    die('This file cannot be accessed directly');
}

function sm_gtm_id(): string
{
    $user = new \WHMCS\Authentication\CurrentUser();
    // No tracking for staff, including Login as Owner sessions.
    if ($user->isAuthenticatedAdmin() || $user->isMasqueradingAdmin()) {
        return '';
    }
    return 'GTM-XXXXXXX';
}

add_hook('ClientAreaHeadOutput', 1, function ($vars) {
    $id = sm_gtm_id();
    if ($id === '') {
        return '';
    }
    return <<<HTML
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{$id}');</script>
HTML;
});

add_hook('ClientAreaHeaderOutput', 1, function ($vars) {
    $id = sm_gtm_id();
    return $id === '' ? '' : '<noscript><iframe src="https://www.googletagmanager.com/ns.html?id='
        . $id . '" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>';
});

The staff check uses CurrentUser, which the developer documentation describes in its Authentication section. Admin pages are excluded already, because ClientArea hooks do not run there. My WHMCS hooks guide covers hook priorities and debugging.

How do you set up GA4 for WHMCS inside GTM?

Add a Google tag with your GA4 Measurement ID, which starts with G-, and fire it on all pages. That records page views across WHMCS with no further PHP. For purchases, Google's GA4 ecommerce guide for Tag Manager describes the setup:

  1. Create a Google Analytics: GA4 Event tag with the event name purchase.
  2. Under More Settings > Ecommerce, tick Send Ecommerce data and choose Data Layer as the source.
  3. Fire it on a Custom Event trigger whose event name is purchase.

How do you push a GA4 purchase event when a WHMCS order completes?

Add a ShoppingCartCheckoutCompletePage hook to the same file. The shopping cart hook reference says it receives the order ID, order number, invoice ID, an ispaid flag, the amount, the payment method and the client's details, and returns HTML for the page. GA4's purchase event requires transaction_id and items, and currency whenever you send value. The hook builds them with two documented API calls, GetOrders and GetClientsDetails:

add_hook('ShoppingCartCheckoutCompletePage', 1, function ($vars) {
    if (sm_gtm_id() === '') {
        return '';
    }
    $orders = localAPI('GetOrders', ['id' => $vars['orderid']]);
    $order  = $orders['orders']['order'][0] ?? null;
    if (!$order) {
        return '';
    }
    $client = localAPI('GetClientsDetails', ['clientid' => $order['userid']]);

    $items = [];
    foreach ($order['lineitems']['lineitem'] ?? [] as $line) {
        $items[] = [
            'item_name'     => $line['product'],
            'item_category' => $line['type'],
            'price'         => (float) $line['amount'],
            'quantity'      => 1,
        ];
    }

    $event = [
        'event'        => 'purchase',
        'invoice_paid' => (bool) $vars['ispaid'],
        'ecommerce'    => [
            'transaction_id' => (string) $vars['ordernumber'],
            'value'          => array_sum(array_column($items, 'price')),
            'currency'       => $client['client']['currency_code'] ?? '',
            'items'          => $items,
        ],
    ];
    $json = json_encode($event, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

    return '<script>window.dataLayer = window.dataLayer || [];'
        . 'dataLayer.push({ecommerce: null});dataLayer.push(' . $json . ');</script>';
});

The {ecommerce: null} push clears the previous ecommerce object, as Google's examples do. Google defines value as the sum of price times quantity, without tax or shipping, so compare it with the invoices of a few test orders that include setup fees and a promotion. Google's events reference says transaction_id helps avoid duplicate events for one purchase, which matters when a customer reloads the page.

What will browser-side tracking in WHMCS miss?

Any purchase whose order complete page never loads in the customer's browser, and any money that never passes through the cart.

  • An order is not a payment. With an offline gateway the page appears before any money arrives. Use invoice_paid as a trigger condition if you only want paid orders.
  • Delayed gateway notices. The addon documentation says that when the gateway has not yet told WHMCS about the payment, the customer is sent to the unpaid invoice with an "awaiting notification" message instead of the complete page. Test every gateway you offer.
  • Renewals. They are invoices, not cart orders, so WHMCS reports stay the source of truth for revenue.
  • Server-side events. The OrderPaid hook runs when the first invoice of a new order is paid, whatever the browser does. Sending that to GA4 needs the Measurement Protocol, which Google says supplements tagging rather than replacing it. For new server-to-server integrations Google now points to its Data Manager API.

Set default consent states before any tag sends data, then update them from the visitor's choice in a banner. Google's consent mode guide covers both parts.

  • Basic or advanced. Basic blocks Google tags until the visitor answers the banner, and sends nothing before that. Advanced loads the tags with defaults set to denied and sends measurements without cookies until consent is granted. Your company's guidelines decide which one you use.
  • With GTM, load the banner in the container. Google recommends a consent mode template from your consent platform or the Community Template Gallery, using setDefaultConsentState and updateConsentState, and says not to use gtag('consent', 'update', ...) in place of updateConsentState. The hook file stays as it is.
  • The four consent types are ad_storage, ad_user_data, ad_personalization and analytics_storage. Defaults can be scoped to regions.
  • Tags without built-in consent checks, which includes many non-Google tags, need checks added in GTM under Advanced > Consent Settings.

Who must see a banner is a legal question, not a WHMCS one. My WHMCS GDPR guide covers the WHMCS side of privacy.

What personal data must stay out of the data layer?

All of it: names, email addresses, phone numbers, postal addresses, tax IDs and card details. The purchase hook receives the full clientdetails array, and every tag in the container can read the data layer, not only GA4. Push the order number, amounts, currency and product names, nothing more. Also look at the addresses of password reset and email verification pages on your install. If they carry a token, exclude those pages in GTM or redact the URL before tags fire.

How do you test GTM and GA4 on WHMCS?

Click Preview in GTM, enter your WHMCS URL and connect Tag Assistant. Tag Assistant also switches on GA4 debug mode, so events appear under Admin > Data display > DebugView. On a staging copy, then again after publishing, check that:

  1. The container loads on the homepage, the cart, the login page and a logged-in page.
  2. The Google tag fires once per page.
  3. With consent denied, no analytics cookies are set.
  4. A test order shows purchase with the right order number, items, value and currency code.
  5. Reloading the complete page does not add a second purchase.
  6. A client in a second currency sends that currency's code.
  7. Pages viewed while logged in as admin contain no container.

If you want server-side purchase events or a consent setup wired into WHMCS, that is WHMCS API integration and automation work, and my hire a WHMCS developer page explains how I scope it.

Last reviewed 12 October 2026. Disclosure: I sell WHMCS and web development work, so I am not neutral. Hook names come from developers.whmcs.com and the GA4, GTM and consent details from Google's documentation as I read them on that date.

Share this article

Shahid Malla

Written by

Shahid Malla

Freelance WHMCS developer and web designer with 13+ years of client work. CTO at ElySpace, freelance developer for Marmaids LLC, and founder of WHMCSPilot and MagizAI.

Got a project like this?

Tell me what you need and I will send a written quote within one business day.