Mahbubur Riad
Back to blog
Hosting & Server 8 min read

WHMCS + Proxmox Integration: Automating VM Provisioning, Billing, and Management – A Complete Guide

Jun 16, 2026 · Mahbubur Riad

Learn how to integrate WHMCS with Proxmox to automate VM provisioning, suspension, and billing for your hosting business. Step-by-step guide with code examples.

On this page

WHMCS + Proxmox Integration: Automating VM Provisioning, Billing, and Management – A Complete Guide

Running a VPS hosting business means juggling provisioning, billing, and support. Doing it manually eats time and invites errors. By integrating WHMCS (the industry‑standard billing and automation platform) with Proxmox VE (a powerful open‑source virtualization platform), you can automate the entire lifecycle of a virtual machine—from order to termination—while keeping invoices in sync.

This guide walks you through a step‑by‑step setup of a WHMCS module for Proxmox, covering prerequisites, API configuration, module creation, product mapping, testing, and ongoing maintenance. Real code examples are included so you can copy‑paste and adapt them to your environment.


Prerequisites

Before you start, make sure you have:

Item Minimum version Notes
WHMCS 8.0+ Latest stable release recommended
Proxmox VE 7.0+ Ensure the pve-api service is running
PHP 7.4+ WHMCS requires PHP 7.4 or newer
cURL extension enabled Used for API calls
A domain or subdomain pointing to your WHMCS install (optional but recommended for callbacks)
SSH root access to the Proxmox host (for initial API token creation)

You should also have a basic understanding of WHMCS products, services, and hooks, as well as comfort editing PHP files.


1. Enable the Proxmox API

Proxmox provides a RESTful API that WHMCS will call to create, suspend, unsuspend, and terminate VMs.

1.1 Create an API Token

SSH into your Proxmox host and run:

Bash
# Replace 'whmcs' with any identifier you like
pveum token add whmcs whmcs-token --privilege=Administrator

You’ll receive output similar to:

Text
token: whmcs!whmcs-token
value: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Store the token ID (whmcs!whmcs-token) and the secret (a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6) securely; you’ll need them in WHMCS.

1.2 Verify API Access

Test the token with curl:

Bash
curl -k -H "Authorization: PVEAPIToken=whmcs!whmcs-token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
     https://YOUR_PROXMOX_HOST:8006/api2/json/nodes

If you see a JSON list of nodes, the token works.


2. Set Up WHMCS for Custom Modules

WHMCS looks for custom modules in /modules/servers/. We’ll create a new server module called proxmox.

2.1 Create the Module Directory

Bash
mkdir -p /path/to/whmcs/modules/servers/proxmox

2.2 Create the Main Module File

Create /path/to/whmcs/modules/servers/proxmox/proxmox.php with the following boilerplate:

PHP
<?php
/**
 * WHMCS Proxmox Server Module
 *
 * @package    WHMCS
 * @subpackage Module/Server/Proxmox
 */

use WHMCS\Module\Server\Proxmox\ProxmoxAPI;

if (!defined("WHMCS")) {
    die("This file cannot be accessed directly");
}

/**
 * Module Configuration Array
 *
 * @return array
 */
function proxmox_config()
{
    return [
        'FriendlyName' => ['Type' => 'System', 'Value' => 'Proxmox VE'],
        'api_host' => [
            'FriendlyName' => 'Proxmox Host',
            'Type' => 'text',
            'Size' => '25',
            'Description' => 'Hostname or IP of your Proxmox VE server (e.g., proxmox.example.com)',
        ],
        'api_port' => [
            'FriendlyName' => 'API Port',
            'Type' => 'text',
            'Size' => '5',
            'Default' => '8006',
            'Description' => 'Port on which the Proxmox API listens (default 8006)',
        ],
        'api_token_id' => [
            'FriendlyName' => 'API Token ID',
            'Type' => 'text',
            'Size' => '30',
            'Description' => 'Token ID in the format user!token-name',
        ],
        'api_token_secret' => [
            'FriendlyName' => 'API Token Secret',
            'Type' => 'text',
            'Size' => '50',
            'Description' => 'Secret value generated when creating the token',
        ],
        'node' => [
            'FriendlyName' => 'Default Node',
            'Type' => 'text',
            'Size' => '20',
            'Description' => 'Proxmox node where VMs will be created (leave blank to let WHMCS choose)',
        ],
    ];
}

/**
 * Module Activation Hook
 *
 * @param array $params
 */
function proxmox_activate($params)
{
    // No special activation needed
}

/**
 * Module Deactivation Hook
 *
 * @param array $params
 */
function proxmox_deactivate($params)
{
    // No special deactivation needed
}

/**
 * Create a VM
 *
 * @param array $params
 * @return string
 */
function proxmox_create_account($params)
{
    $serverParams = $params['server'];
    $api = new ProxmoxAPI(
        $serverParams['api_host'],
        $serverParams['api_port'],
        $serverParams['api_token_id'],
        $serverParams['api_token_secret']
    );

    // Extract product configurable options
    $vmid = $params['configoption1']; // Assume configoption1 holds desired VMID (or 0 for auto)
    $hostname = $params['domain']; // WHMCS passes the domain as hostname
    $cores = $params['configoption2']; // Number of CPU cores
    $ram = $params['configoption3'] * 1024; // WHMCS stores RAM in MB, convert to MB for API
    $disk = $params['configoption4'] * 1024; // Disk in GB -> MB
    $template = $params['configoption5']; // Template ID (e.g., 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.gz')

    // If VMID not set, let Proxmox assign next available
    if (empty($vmid) || $vmid == 0) {
        $vmid = $api->getNextVmid();
    }

    // Create the VM (using QEMU for simplicity)
    $api->createVm(
        $vmid,
        $hostname,
        $cores,
        $ram,
        $disk,
        $template,
        $serverParams['node'] ?? null
    );

    // Return success
    return "success";
}

/**
 * Suspend a VM
 *
 * @param array $params
 * @return string
 */
function proxmox_suspend_account($params)
{
    $serverParams = $params['server'];
    $api = new ProxmoxAPI(
        $serverParams['api_host'],
        $serverParams['api_port'],
        $serverParams['api_token_id'],
        $serverParams['api_token_secret']
    );

    $vmid = $params['serviceid']; // WHMCS passes the service ID as VMID
    $api->setVmStatus($vmid, 'stop');

    return "success";
}

/**
 * Unsuspend a VM
 *
 * @param array $params
 * @return string
 */
function proxmox_unsuspend_account($params)
{
    $serverParams = $params['server'];
    $api = new ProxmoxAPI(
        $serverParams['api_host'],
        $serverParams['api_port'],
        $serverParams['api_token_id'],
        $serverParams['api_token_secret']
    );

    $vmid = $params['serviceid'];
    $api->setVmStatus($vmid, 'start');

    return "success";
}

/**
 * Terminate a VM
 *
 * @param array $params
 * @return string
 */
function proxmox_terminate_account($params)
{
    $serverParams = $params['server'];
    $api = new ProxmoxAPI(
        $serverParams['api_host'],
        $serverParams['api_port'],
        $serverParams['api_token_id'],
        $serverParams['api_token_secret']
    );

    $vmid = $params['serviceid'];
    $api->deleteVm($vmid);

    return "success";
}

/**
 * Change Package (upgrade/downgrade)
 *
 * @param array $params
 * @return string
 */
function proxmox_change_package($params)
{
    // For simplicity, we treat change as a suspend + create with new specs
    // In a production module you would resize the existing VM.
    proxmox_terminate_account($params);
    return proxmox_create_account($params);
}

/**
 * Admin Dashboard Output (optional)
 *
 * @param array $params
 * @return string
 */
function proxmox_admin_panel($params)
{
    return '<div class="panel panel-default"><div class="panel-heading">Proxmox VE Server</div><div class="panel-body">Module is active.</div></div>';
}

2.3 Helper Class – ProxmoxAPI

Create a separate file /path/to/whmcs/modules/servers/proxmox/proxmoxapi.php to encapsulate API calls:

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

class ProxmoxAPI
{
    protected $host;
    protected $port;
    protected $tokenId;
    protected $tokenSecret;
    protected $baseUrl;

    public function __construct($host, $port = 8006, $tokenId, $tokenSecret)
    {
        $this->host = rtrim($host, '/');
        $this->port = $port;
        $this->tokenId = $tokenId;
        $this->tokenSecret = $tokenSecret;
        $this->baseUrl = sprintf("https://%s:%d/api2/json", $this->host, $this->port);
    }

    /**
     * Make a GET request
     */
    private function get($endpoint)
    {
        $url = $this->baseUrl . $endpoint;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For self‑signed certs; adjust in production
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            "Authorization: PVEAPIToken={$this->tokenId}={$this->tokenSecret}",
            "Content-Type: application/json"
        ]);
        $result = curl_exec($ch);
        curl_close($ch);
        return json_decode($result, true);
    }

    /**
     * Make a POST request
     */
    private function post($endpoint, $data = [])
    {
        $url = $this->baseUrl . $endpoint;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            "Authorization: PVEAPIToken={$this->tokenId}={$this->tokenSecret}",
            "Content-Type: application/json"
        ]);
        $result = curl_exec($ch);
        curl_close($ch);
        return json_decode($result, true);
    }

    /**
     * Get next available VMID
     */
    public function getNextVmid()
    {
        $res = $this->get('/cluster/nextid');
        return $res['data'] ?? null;
    }

    /**
     * Create a VM (QEMU)
     */
    public function createVm($vmid, $hostname, $cores, $ramMb, $diskMb, $template, $node = null)
    {
        $node = $node ?? $this->getFirstNode();
        $payload = [
            'vmid' => $vmid,
            'name' => $hostname,
            'cores' => $cores,
            'memory' => $ramMb,
            'net0' => 'virtio,bridge=vmbr0',
            'scsi0' => sprintf("%s:%d", $template, $diskMb),
            'ostype' => 'l26',
            'boot' => 'order=scsi0;net0',
        ];
        $this->post("/nodes/{$node}/qemu", $payload);
    }

    /**
     * Set VM status (start/stop/shutdown)
     */
    public function setVmStatus($vmid, $action)
    {
        $node = $this->getNodeForVm($vmid);
        $this->post("/nodes/{$node}/qemu/{$vmid}/status/{$action}", []);
    }

    /**
     * Delete a VM
     */
    public function deleteVm($vmid)
    {
        $node = $this->getNodeForVm($vmid);
        $this->post("/nodes/{$node}/qemu/{$vmid}", ['delete' => 1]);
    }

    /**
     * Retrieve the first node (used when none is specified)
     */
    private function getFirstNode()
    {
        $res = $this->get('/nodes');
        if (!empty($res['data'])) {
            return $res['data'][0]['node'];
        }
        throw new Exception('No nodes found in Proxmox cluster');
    }

    /**
     * Find which node a VM resides on
     */
    private function getNodeForVm($vmid)
    {
        $res = $this->get('/cluster/resources?type=vm');
        foreach ($res['data'] as $vm) {
            if ((int)$vm['vmid'] === (int)$vmid) {
                return $vm['node'];
            }
        }
        throw new Exception("VMID {$vmid} not found on any node");
    }
}

Security Note: The example disables SSL verification (CURLOPT_SSL_VERIFYPEER => false) for simplicity. In production, install a valid certificate on your Proxmox host and set this option to true, or add the CA certificate to cURL’s trust store.


3. Register the Server in WHMCS

  1. Log into your WHMCS admin area.
  2. Navigate to Setup → Products/Services → Servers.
  3. Click Add New Server.
  4. Fill in:
    • Hostname: Your Proxmox host (e.g., proxmox.example.com)
    • Port: 8006
    • Type: Select Proxmox VE (the friendly name we defined).
    • Username: Your API Token ID (e.g., root!pam@whmcs-token – note WHMCS expects the token ID exactly as created).
    • Password: Your API Token Secret.
  5. Save. WHMCS will test the connection; you should see a “Connection Successful” message.

4. Create a Product/Service

4.1 Define Configurable Options

Go to Setup → Products/Services → Configurable Options and create options that map to VM resources:

Option Name Type Description
CPU Cores Dropdown 1, 2, 4, 8
RAM (MB) Dropdown 512, 1024, 2048, 4096
Disk (GB) Dropdown 10, 20, 40, 80
OS Template Dropdown List of templates available on your Proxmox storage (e.g., local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.gz)
Desired VMID (optional) Text Leave blank for auto‑assign

4.2 Create the Product

  1. Setup → Products/Services → Products/Services → Create a New Product.
  2. Product Type: Hosting Account.
  3. Name: e.g., “Proxmox VPS – Basic”.
  4. Module Settings: Choose the server you just added (Proxmox VE).
  5. Assign Configurable Options: Add the options you created.
  6. Pricing: Set monthly/annual rates as desired.
  7. Save.

5. Automation: Cron and Hooks

WHMCS relies on its cron to trigger provisioning, suspension, and termination based on order status and service dates.

5.1 Ensure Cron Is Running

Add the following to your server’s crontab (crontab -e):

Text
*/5 * * * * php /path/to/whmcs/admin/cron.php

This runs every five minutes, checking for pending orders, suspensions, etc.

5.2 Optional: Provisioning Hook for Immediate Creation

If you want VMs to be created immediately upon payment (instead of waiting for the next cron), create a hook file /path/to/whmcs/includes/hooks/provisionvm.php:

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

add_hook('AfterPaymentConfirmation', 1, function($vars) {
    $invoiceId = $vars['invoiceid'];
    // Load invoice details to see if it's a hosting product
    $invoice = Capsule::table('tblinvoices')
        ->join('tblinvoiceitems', 'tblinvoiceitems.invoiceid', '=', 'tblinvoices.id')
        ->where('tblinvoices.id', $invoiceId)
        ->first();

    if ($invoice && $invoice->type == 'Hosting Account') {
        $serviceId = $invoice->serviceid;
        // Trigger provisioning via WHMCS API
        $result = localAPI('AcceptOrder', ['serviceid' => $serviceId]);
        // Optionally log
        if ($result['result'] == 'success') {
            logActivity("Proxmox VM provisioned immediately after payment for service ID {$serviceId}");
        }
    }
});

6. Testing the Flow

  1. Place a Test Order (via WHMCS client area or admin → Add Order).
  2. Complete payment (use a test gateway or mark as paid manually).
  3. Watch the WHMCS admin log under Utilities → Logs → Module Log for entries like:
    • proxmox_create_account: success
    • Proxmox API call to /nodes/pve1/qemu returned ...
  4. In Proxmox UI, verify a new VM appears with the correct hostname, CPU, RAM, and disk.
  5. Test suspension: edit the service status to Suspended → check that the VM stops.
  6. Test unsuspension → VM starts.
  7. Test termination → VM disappears from Proxmox.

If any step fails, check:

  • WHMCS Module Log.
  • Proxmox /var/log/pveproxy/access.log and /var/log/pvedaemon/[plugin].log.
  • Ensure the API token has Administrator privileges or at least VM.Audit and VM.Allocate on the target node.

7. Comparison: WHMCS + Proxmox vs Alternatives

Feature WHMCS + Proxmox cPanel/WHM + WHMCS SolusVM Virtualizor
Licensing Cost Free (Proxmox) + WHMCS license cPanel license + WHMCS Paid per node Paid per node
Hypervisor KVM/LXC (open) Primarily KVM via Virtuozzo/OpenVZ (cPanel) KVM/LXC KVM/LXC
API Maturity Stable REST API, well documented WHM API (stable) API available but less extensive API available
Billing Automation Full WHMCS integration WHMCS works but limited to cPanel features Requires third‑party billing bridge Requires third‑party billing bridge
Scalability Cluster‑ready, HA possible Scaling requires additional licenses Good for VPS only Good for VPS only
Learning Curve Moderate (need to understand Proxmox) Low if already using cPanel Low‑moderate Low‑moderate
Community Support Strong open‑source community Large commercial community Niche Niche

For a pure VPS/reseller business focused on cost‑effectiveness and flexibility, WHMCS + Proxmox offers the best balance of zero hypervisor licensing and powerful automation.


8. Ongoing Maintenance & Best Practices

Task Frequency Details
Update Proxmox Monthly Follow the Proxmox update schedule; test updates on a staging node first.
WHMCS Updates As released Always backup before upgrading; test module compatibility.
API Token Rotation Every 90 days Generate a new token, update WHMCS server config, delete old token.
Monitor VM Health Ongoing Use Proxmox’s built‑in monitoring or external tools (e.g., Netdata) to alert on high CPU/RAM/disk usage.
Backup Strategy Daily/Weekly Configure Proxmox backup jobs to a remote storage (NFS, Ceph, or off‑site S3).
Security Hardening Quarterly Disable root SSH login, enable 2FA for PAM, keep firewall rules tight, and restrict API token IPs if possible.
Documentation As needed Keep an internal wiki of your product configurations, template IDs, and any custom hooks.

9. Troubleshooting Common Issues

Symptom Likely Cause Fix
Module returns “Invalid credentials” Token ID/secret mismatch or missing ! separator Verify token format: username!token-name. Ensure no extra spaces.
VM creation fails with “storage not found” Template path incorrect List storage with pvesm list and adjust the template configurable option.
Cron does not trigger provisioning Cron not running or cron.php permissions wrong Check /var/log/syslog for cron entries; ensure php CLI path is correct.
VM suspends but does not stop Using shutdown instead of stop API call Ensure suspend function calls /status/stop.
After termination, VMID remains in WHMCS Terminate hook not deleting associated service Verify that the terminate function returns success and that WHMCS service status updates to “Cancelled”.
SSL handshake failure Self‑signed certificate and verification enabled Either add Proxmox CA to trusted certs or set CURLOPT_SSL_VERIFYPEER => false (not recommended for production).

10. Conclusion

Integrating WHMCS with Proxmox VE transforms a manual VPS provisioning workflow into a seamless, automated operation. By following the steps above—setting up a secure API token, building a lightweight WHMCS server module, mapping configurable options, and leveraging WHMCS cron—you can offer your customers instant VM deployment, accurate billing, and reliable lifecycle management without the overhead of proprietary hypervisor licenses.

Whether you’re a startup launching your first VPS line or an established host looking to cut costs, this stack provides a solid, open‑source foundation that scales with your business.

For more tutorials on hosting automation, billing, and server management, keep an eye on mahbuburriad.com.


FAQ

Q1: Do I need a paid Proxmox license to use the API?
A: No. Proxmox VE is open source; its REST API is freely available in the community edition. Only the optional Proxmox Backup Server and certain enterprise repositories require a subscription.

Q2: Can I manage LXC containers instead of QEMU VMs with this module?
A: Absolutely. The helper class can be adjusted to call /nodes/{node}/lxc endpoints and use LXC‑specific settings (e.g., features: nesting=1). The provisioning function would change the payload accordingly.

Q3: How do I handle multiple Proxmox nodes in a cluster?
A: Leave the “Default Node” field blank in the WHMCS server configuration. The module’s getFirstNode() helper will pick the first node, but you can enhance it to select the node with the most free resources by querying /nodes/{node}/status.

Q4: Is it possible to resize an existing VM without deleting it?
A: Yes, but it requires additional API calls (stop, resize disk, start). For a production‑ready module you would implement a resize_account function that powers off the VM, updates the disk or memory settings via the API, then restarts it.

Q5: What about backups and snapshots—can WHMCS trigger those?
A: WHMCS does not have native actions for snapshots, but you can add custom admin actions or hooks that call the Proxmox snapshot API (/nodes/{node}/qemu/{vmid}/snapshot). This can be exposed as a configurable add‑on product.


Word count: ~1,730

Related

Related posts