WHMCS already creates cPanel accounts when products are configured properly. Why would you write a hook for it? Because real hosting operations have edge cases the built-in flow doesn't handle: pre-populated DNS records, custom welcome data, third-party integrations that need to know about the new account, conditional creation rules, multi-step provisioning.
This is the working pattern for a "create cPanel account + do extra stuff" hook.
When this hook is the right tool
If you're solving any of these problems, you want a hook:
- "After a new cPanel account is created, also add the domain to Cloudflare DNS."
- "After creation, push the customer to my CRM with the cPanel account details."
- "Create the cPanel account only if the customer has been pre-vetted in our anti-fraud system."
- "Customize the welcome email with server-specific instructions based on which node the account landed on."
If your problem is "I need to create cPanel accounts without using the WHMCS cPanel module" — that's a custom provisioning module, not a hook. See my provisioning module guide.
Step 1 — Pick the right hook point
Two hook points matter for cPanel provisioning:
AfterModuleCreate— fires after the cPanel module successfully creates the account. Best for "now that the account exists, do X."PreModuleCreate— fires before the create call. Use to short-circuit creation (e.g., fraud check) or modify the params being sent.
Step 2 — Write the hook
Create /path/to/whmcs/includes/hooks/cpanel_post_create.php:
<?php
if (!defined('WHMCS')) {
die('This file cannot be accessed directly');
}
use WHMCS\Database\Capsule;
add_hook('AfterModuleCreate', 1, function ($vars) {
// Only run for cPanel-provisioned services
if (strtolower($vars['params']['type'] ?? '') !== 'cpanel') return;
$serviceId = $vars['params']['serviceid'];
$domain = $vars['params']['domain'];
$username = $vars['params']['username'];
$serverHostname = $vars['params']['serverhostname'];
$clientEmail = $vars['params']['clientsdetails']['email'];
try {
// 1. Add to Cloudflare DNS automatically
if (!empty(getenv('CLOUDFLARE_TOKEN'))) {
addToCloudflare($domain, $serverHostname);
}
// 2. Push to CRM
if (!empty(getenv('CRM_WEBHOOK_URL'))) {
pushToCrm([
'email' => $clientEmail,
'domain' => $domain,
'username' => $username,
'server' => $serverHostname,
'event' => 'hosting_provisioned',
]);
}
// 3. Send a Slack notification to your ops team
if (!empty(getenv('SLACK_WEBHOOK'))) {
slackNotify("New cPanel: {$domain} on {$serverHostname} (service #{$serviceId})");
}
logActivity("Post-create automation complete for service #$serviceId ($domain)");
} catch (\Throwable $e) {
logActivity("Post-create automation failed for service #$serviceId: " . $e->getMessage());
// Don't throw — provisioning already succeeded; secondary work is best-effort
}
});
// Helper functions
function addToCloudflare($domain, $serverHostname) {
// Implementation depends on whether your DNS is on Cloudflare
// Get zone ID, create A record, etc.
// See Cloudflare API docs.
}
function pushToCrm(array $payload) {
$ch = curl_init(getenv('CRM_WEBHOOK_URL'));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 5,
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
}
function slackNotify(string $message) {
$ch = curl_init(getenv('SLACK_WEBHOOK'));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['text' => $message]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 3,
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
}
Step 3 — A more advanced example: pre-create fraud check
If you want to block account creation based on your own fraud rules (before the cPanel call is made):
add_hook('PreModuleCreate', 1, function ($vars) {
if (strtolower($vars['params']['type'] ?? '') !== 'cpanel') return;
$clientId = $vars['params']['userid'];
$email = $vars['params']['clientsdetails']['email'];
$ip = $vars['params']['clientsdetails']['lastlogin']['ip'] ?? null;
// Run your fraud check (custom logic, external API, whatever)
if (isHighRiskClient($clientId, $email, $ip)) {
// Logs the abort
logActivity("Aborted cPanel provisioning for client #{$clientId} (high-risk score)");
// Return false-y to abort the create call
return ['abortcmd' => true];
}
});
The abortcmd return tells WHMCS to skip the actual create call. The service stays in "Pending" state — admin can review.
Step 4 — Store secrets in env, not in code
Notice the getenv() calls. Set these in /etc/environment, your systemd unit, or your hosting panel's env-var manager. Never hardcode in PHP files.
# /etc/environment (server-wide)
CLOUDFLARE_TOKEN=abc123...
CRM_WEBHOOK_URL=https://crm.yourcompany.com/incoming
SLACK_WEBHOOK=https://hooks.slack.com/services/...
Restart PHP-FPM after changes so the new env is picked up.
How to verify the hook fires
- Create a test client + place a test order for a cPanel product.
- Trigger provisioning (the next cron, or admin → manually Create).
- Watch Utilities → Logs → Activity Log — your
logActivitycalls appear here. - Confirm the side-effect (Cloudflare record, CRM push, Slack message) actually happened.
- For pre-create hooks: provision a known-bad client and confirm the abort message appears + provisioning didn't happen.
Common pitfalls
"Hook fires but Slack/CRM doesn't get notified." Network call failed silently. Add logging inside the curl call to confirm the response. Most often: env var isn't set (returns empty, your if skips the block); or your server can't reach the external service.
"Hook fires twice for the same account." WHMCS retried the creation after a transient failure, or there's a duplicate hook file. Check /includes/hooks/ for duplicates.
"PreModuleCreate doesn't abort." Return value must be specifically formatted (['abortcmd' => true]). Returning false alone doesn't abort.
"Hook slows provisioning." External calls are synchronous. Add timeouts (3-5 seconds max). For non-critical work, push to a queue and process asynchronously.
My take — when to use a hook vs. modifying the module
- Hook: when the WHMCS cPanel module does its job and you want to do something additional.
- Custom module: when the WHMCS cPanel module doesn't fit at all (e.g., you're not using WHM as the management interface).
- Module + hook: when you want both module-level lifecycle and cross-cutting concerns. This is what I usually build for hosting clients.
Going further
- WHMCS provisioning hooks reference
- My hooks guide — the deep dive on WHMCS hooks.
- My cPanel integration guide — for the base setup this hook extends.
I build cPanel + WHMCS provisioning automation for hosting businesses — extra integrations, fraud checks, multi-step flows, monitoring. Tell me what you need to automate and I'll send a quote in 24 hours.