Skip to content
Shahid Malla

WHMCS Hooks Guide: How Do You Run Code on WHMCS Events?

A checked guide to WHMCS hooks: the includes/hooks folder, add_hook and priorities, nine common hook points, examples for InvoicePaid, ClientAreaHeadOutput and DailyCronJob, and when a hook is the wrong tool.

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

A WHMCS hook is a PHP function that WHMCS runs when a named event happens, such as a client signing up or an invoice being paid. You save the function in a file in the includes/hooks/ directory and register it with add_hook(), passing the hook point name, a priority and the function. No file that WHMCS ships is edited.

I have built 200+ custom WHMCS modules. I checked every hook name, parameter and menu path below against the official WHMCS documentation on 10 October 2026.

What is a WHMCS hook?

A WHMCS hook is your own function attached to a hook point, which is a named moment in WHMCS such as ClientAdd or InvoicePaid. When WHMCS reaches that moment, it calls your function and passes an array of variables that describe what just happened.

The official hook index listed 339 hook points when I counted them. Each has a reference entry with the parameters your function receives, the response it may return, and sample code. Many entries say "No response supported". Those hooks can react to an event but cannot change its result. Others accept a return value that changes what WHMCS does next.

Where do WHMCS hook files go?

Hook files go in the /includes/hooks/ directory of the WHMCS installation, as .php files with names you choose. The getting started page adds one useful detail: a file whose name starts with an underscore is excluded from execution. Renaming orders.php to _orders.php switches a hook file off without deleting it.

A module can carry its own hooks in a file named hooks.php in the root of the module's directory, which WHMCS loads on every page load. There is a catch: the module hooks page says the file is detected when the module is activated. If you add hooks.php to a provisioning module that is already in use, open a product that uses it at Configuration > System Settings > Products/Services, go to the Module Settings tab and save the product again. Older guides that send you to "Setup > ..." describe the previous admin menu.

How does add_hook work in WHMCS?

add_hook() takes three arguments: the hook point name, a priority, and the function to call.

add_hook('ClientAdd', 1, function ($vars) {
    // $vars['client_id'], $vars['email'], $vars['firstname'] ...
});
  • Hook point. Copy the name exactly from the hook index.
  • Priority. An integer that sets the order when several functions share a hook point. The documentation says it can be any valid integer but not which end runs first, so test the order on a staging copy if your code depends on it.
  • Function. A closure, or the name of a function as a string. Give named functions your own prefix, because two files that declare the same function name cause a PHP fatal error.

Which WHMCS hook points are used most?

These nine cover most of the hook work I am asked for. The last column decides whether a hook can change what WHMCS does or can only react to it.

Hook pointWhen it runsWhat your function may return
ClientAddAs a client is being added. Receives the client's details, including client_id and email.Nothing. No response supported.
ShoppingCartValidateCheckoutDuring checkout completion, before the order and invoice are created.A string, or an array of strings, shown to the buyer as errors. The order is not created.
InvoicePaidWhen an invoice is paid, after the receipt email. Receives invoiceid.Nothing.
PreModuleCreateBefore a provisioning module's create function runs for a service.Values that override the module parameters, or abortcmd set to true to stop the action.
AfterModuleCreateAfter the create function succeeds. Receives params. AfterModuleCreateFailed also receives failureResponseMessage.Nothing.
EmailPreSendBefore any templated email is sent. Receives messagename, relid and mergefields.Extra merge fields, or abortsend set to true to stop the email.
ClientAreaPageOn all pages of the client area.Values that become extra Smarty template variables.
ClientAreaHeadOutputWhen a client area page is being output.HTML placed inside the page's HEAD tags.
DailyCronJobAt the very end of the daily automation cron run. No parameters.Nothing.

The parameters differ for every hook point, so read the entry in the hooks reference before you write the function. Guessed key names are a common reason a hook does nothing. EmailPreSend, for example, receives three parameters, and the email body is not one of them.

How do I write a WHMCS hook file?

Create one PHP file in includes/hooks/, block direct access, and call add_hook(). This example tells an outside accounting system each time an invoice is paid.

<?php
// includes/hooks/acme_invoice_paid.php

if (!defined('WHMCS')) {
    die('You cannot access this file directly.');
}

add_hook('InvoicePaid', 1, function ($vars) {
    $invoiceId = (int) $vars['invoiceid'];

    $ch = curl_init('https://accounts.example.com/whmcs/invoice-paid');
    curl_setopt_array($ch, [
        CURLOPT_POSTFIELDS     => ['invoice_id' => $invoiceId],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
    ]);
    curl_exec($ch);
    $error = curl_error($ch) ?: 'sent';

    logActivity("acme_invoice_paid: invoice {$invoiceId}: {$error}");
});

The defined('WHMCS') check comes from the official sample hook and stops the file from doing anything when someone requests it directly. The timeout is my own habit: the hook runs in the same PHP process that marked the invoice paid, so a slow endpoint holds that process until it answers. If you need to act before the receipt email, the hook point is InvoicePaidPreEmail.

How do I add CSS or JavaScript with the ClientAreaHeadOutput hook?

Return a string of HTML from a ClientAreaHeadOutput hook, and WHMCS outputs it inside the HEAD tags of the client area page. That lets you load a stylesheet, a verification meta tag or a script without editing a theme file.

add_hook('ClientAreaHeadOutput', 1, function ($vars) {
    $href = $vars['WEB_ROOT'] . '/acme-custom/client.css';

    return '<link rel="stylesheet" href="' . $href . '">';
});

The function receives the template variables common to all client area pages, among them filename, pagetitle, template and WEB_ROOT, the web path to the WHMCS root. Use them to limit the output to the pages that need it. Two siblings work the same way: ClientAreaHeaderOutput returns HTML for the top of the body, and ClientAreaFooterOutput returns HTML placed before the closing body tag. All three are in the output hooks reference. They run on every client area page, so keep database queries and outside calls out of them.

When should I use the DailyCronJob hook?

Use DailyCronJob for work that should happen once a day and does not belong to one client action, such as a daily export, a cleanup of your own tables or a summary for staff. The reference says it runs at the very end of the daily automation cron run, receives no parameters and supports no response.

add_hook('DailyCronJob', 1, function ($vars) {
    $result = localAPI('GetInvoices', ['status' => 'Overdue', 'limitnum' => 1]);

    logActivity('acme_daily: overdue invoices = ' . ($result['totalresults'] ?? 'unknown'));
});

That example calls the WHMCS API from inside the hook through localAPI(), which I cover in my WHMCS API guide. Related hook points: PreCronJob runs before the daily automation, AfterCronJob runs each time the system cron is called, and DailyCronJobPreEmail can return true to suppress the cron email report. None of them fire if the WHMCS cron itself is not running, so check that first when a daily job looks dead.

How do I check that a WHMCS hook ran?

Write a line to the activity log from inside the hook, then read the log. logActivity('message', $clientId) is documented on the logging page as available to all hooks and modules, and the second argument is an optional client ID. The entries appear in the Activity Log at Configuration > System Logs. If the line never appears, work through this list.

  1. Is the file in includes/hooks/, with a .php extension and no leading underscore?
  2. Does php -l yourfile.php report no syntax errors?
  3. Is the hook point name copied exactly from the hook index?
  4. Is it the event you think it is? A part payment triggers AddInvoicePayment. InvoicePaid waits until the invoice is paid.
  5. Turn on Hooks Debug Mode on the Other tab at Configuration > System Settings > General Settings. It logs hook system errors to the Activity Log and can create a large number of entries, so turn it off when you finish.

When is a WHMCS hook the wrong tool?

A hook is the wrong tool when the job is more than a reaction to one event.

  • Accounts on another system have to be created, suspended and terminated. That is a provisioning module, covered in my guide to building a WHMCS provisioning module.
  • Another server has to read or change WHMCS data. That is the external API, not a hook.
  • The feature needs its own settings or admin screen. That is an addon module, which can ship a hooks.php of its own.

Hooks also carry risks that you should plan for.

  • No response means no control. You cannot stop a signup from ClientAdd. Pick a hook point whose reference entry accepts a return value.
  • A PHP fatal error in a hook file can break the pages that load it. Test on a staging copy, and keep SFTP access so you can rename the file with a leading underscore.
  • Hook data can be sensitive. ClientAdd passes a password field, and ShoppingCartValidateCheckout passes card fields such as ccnumber. Never write the whole $vars array to a log or send it to an outside service.

If you want a hook written, or an old core edit moved into one, that is part of my WHMCS custom module development work, and single-event jobs usually fit hourly WHMCS developer work; the scoping call of about 30 minutes is free and a written quote follows within one business day.

Last reviewed 10 October 2026. Disclosure: I am a freelance WHMCS developer and I sell hook and module development at $55-$65 per hour or a fixed quote, so I am not neutral. I am CTO at ElySpace, a freelance developer for Marmaids LLC, and founder of WHMCSPilot and MagizAI.

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.