Hooks are WHMCS's extensibility system. They let you run custom PHP code at specific events — client signup, invoice creation, ticket reply, etc. — without modifying WHMCS's core files. This article covers the basic hook patterns that solve common reseller customization needs.
Why use hooks
- Survives upgrades. WHMCS upgrades replace core files. Hooks live in your
includes/hooks/directory, untouched. - Compose with other extensions. Multiple hooks can fire at the same event without stepping on each other.
- Standard pattern. Anyone hiring a developer to customize WHMCS for you will write hooks.
Hook structure
A hook is a PHP file in includes/hooks/ that registers a function with WHMCS's event system:
<?php
// File: includes/hooks/my_first_hook.php
add_hook('ClientAdd', 1, function($vars) {
// $vars contains the new client's data
// Do something with it
return [];
});
That's it. Drop the file in includes/hooks/, hit Save in your code editor, and the hook is active. WHMCS auto-discovers hooks at the path on each request.
Anatomy of a hook call
- Event name (e.g.,
ClientAdd) — when WHMCS fires the hook - Priority (integer; lower runs first; default 1)
- Callback function — receives
$varsarray containing context - Return value — usually empty array; some hook types let you modify behavior by returning specific structures
Common hook events
WHMCS has 200+ hook events. The most useful for a typical reseller:
Client lifecycle
ClientAdd— client signs upClientEdit— client info updatedClientLogin— client logs inClientClose— client account closed
Service lifecycle
AfterModuleCreate— cPanel account just created on your serverAfterModuleSuspend— account suspendedAfterModuleUnsuspend— account unsuspendedAfterModuleTerminate— account terminated
Billing
InvoiceCreated— new invoice generatedInvoicePaid— invoice marked as paidInvoiceCancelled— invoice cancelled
Support
TicketOpen— new ticket submittedTicketAdminReply— admin posts a replyTicketUserReply— client posts a reply
Cron / automation
DailyCronJob— runs once per dayAfterCronJob— runs after every 5-minute cron
Useful hook examples
Send a Slack notification on new signup
<?php
add_hook('ClientAdd', 1, function($vars) {
$payload = json_encode([
'text' => "New signup: {$vars['firstname']} {$vars['lastname']} ({$vars['email']})"
]);
$ch = curl_init('https://hooks.slack.com/services/YOUR/WEBHOOK/URL');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
return [];
});
Email yourself when a high-value invoice is paid
<?php
add_hook('InvoicePaid', 1, function($vars) {
if ($vars['amount'] > 100) {
mail('you@yourdomain.com',
"Big invoice paid: ${$vars['amount']}",
"Invoice ID: {$vars['invoiceid']}
Client: {$vars['userid']}");
}
return [];
});
Add a custom welcome message to all new accounts
<?php
add_hook('AfterModuleCreate', 1, function($vars) {
$client_email = WHMCSDatabaseCapsule::table('tblclients')
->where('id', $vars['userid'])
->value('email');
mail($client_email,
'Welcome to YourBrand',
'Thanks for choosing us! Here are some getting-started resources...');
return [];
});
Auto-tag tickets containing the word "urgent"
<?php
add_hook('TicketOpen', 1, function($vars) {
if (stripos($vars['message'], 'urgent') !== false) {
WHMCSDatabaseCapsule::table('tbltickets')
->where('id', $vars['ticketid'])
->update(['priority' => 'High']);
}
return [];
});
Database access in hooks
WHMCS exposes the Laravel Capsule database layer:
// SELECT
$client = WHMCSDatabaseCapsule::table('tblclients')->where('id', $vars['userid'])->first();
// UPDATE
WHMCSDatabaseCapsule::table('tblclients')->where('id', $vars['userid'])->update(['notes' => 'New note']);
// INSERT
WHMCSDatabaseCapsule::table('tbl_my_custom_table')->insert([
'field1' => 'value1',
'created' => date('Y-m-d H:i:s'),
]);
Avoid raw mysql_ functions or $query = "SELECT..." string-building — you'll create SQL injection vulnerabilities and your hook will break on WHMCS upgrades.
Logging from hooks
Don't use echo or var_dump — they break the page rendering. Instead:
logActivity("My hook did something interesting: " . print_r($vars, true), 0);
Logs appear in WHMCS Admin → Utilities → Logs → Activity Log.
Security considerations
- Never trust hook input directly. Validate and escape any data you pass to
mail(), database queries, or external APIs. - Use prepared statements. Capsule handles this automatically when you use the query builder.
- Don't expose sensitive data. Logging full client objects exposes PII in logs. Filter what you log.
- Use HTTPS for outbound webhooks. Slack, Discord, etc. all require HTTPS — make sure your
curlcalls use it.
Hook discovery and debugging
- WHMCS Admin → Utilities → Logs → Module Log shows hook execution and errors.
- Hook errors silently fail by default. Wrap your hook in
try/catchand log exceptions. - The full hook reference: developers.whmcs.com/hooks
What to put in hooks vs. not
Good fits:
- Triggering external APIs (Slack, Discord, custom CRM)
- Custom email notifications beyond WHMCS templates
- Auto-tagging or auto-categorizing tickets
- Custom analytics events
- Provisioning auxiliary services (DNS records, monitoring entries)
Not good fits:
- Modifying WHMCS's built-in workflows (use module hooks or custom modules instead)
- Heavy database operations (run them in a cron, not inline in user-facing requests)
- Anything time-sensitive that should fail visibly — hooks are silent on failure
Common pitfalls
- Hook file outside includes/hooks/. WHMCS auto-discovers hooks only in that directory.
- Wrong PHP version. Hooks must work with the PHP version your WHMCS is running on. Test locally before deploying.
- Slow external APIs blocking page loads. A signup flow that calls Slack synchronously will appear slow. Use
curl_multi_*or background jobs for slow external calls. - Forgotten hooks after disabling a feature. If you wrote a hook for a feature you no longer use, remove the file. Orphaned hooks add complexity for the next person.