Shahid Malla

WHMCS Hook: Automate cPanel Account Side-Effects

WHMCS already creates cPanel accounts. The hook that adds DNS auto-config, CRM push, Slack notifications, fraud screening — all the cross-cutting work the module doesn't do.

S Shahid Malla
· Feb 8, 2026 · 5 min read · 89 views
shahidmalla.com/blog/whmcs-hook-automate-cpanel-account-side-effects
WHMCS Hook: Automate cPanel Account Side-Effects
On this page (9 sections)

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

  1. Create a test client + place a test order for a cPanel product.
  2. Trigger provisioning (the next cron, or admin → manually Create).
  3. Watch Utilities → Logs → Activity Log — your logActivity calls appear here.
  4. Confirm the side-effect (Cloudflare record, CRM push, Slack message) actually happened.
  5. 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


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.

Share this article

S

Written by

Shahid Malla

WHMCS expert, full-stack developer, technical lead at Fada.cloud. 10+ years building hosting platforms, custom modules, and automation that ships.

Trusted platforms

Prefer to hire through a platform?

Not sure about working directly? Hire me through Fiverr or Upwork instead - same me, same work, with the platform's buyer protection and escrow.

Got a project like this?

Tell me what you need - I'll send a real quote within 24 hours.