Mahbubur Riad
Back to blog
Hosting & Server 7 min read

Step‑By‑Step Guide: Automating WHMCS Client Onboarding with Terraform for Seamless Server Provisioning

Jun 22, 2026 · Mahbubur Riad

Learn how to connect WHMCS to Terraform so a new VPS, networking and DNS are provisioned automatically the moment a client places an order.

On this page

Why Automate WHMCS Onboarding?

Every time a client clicks Buy in WHMCS you (or your support team) have to:

  1. Spin up a new VPS or virtual machine.
  2. Attach it to the right VLAN or private network.
  3. Create DNS records so the domain resolves to the new IP.

Doing this manually adds latency, invites human error, and scales terribly. With Terraform you can codify the entire lifecycle, keep it version‑controlled, and let WHMCS act as the trigger. The result? A client gets a fully working server seconds after payment, and you get a reproducible, auditable process.

In this guide we’ll walk through the whole pipeline:

  • WHMCS order hook → PHP webhook → Terraform Cloud run → VPS, network & DNS creation.

You’ll finish with a ready‑to‑use repository that you can adapt to any cloud provider that Terraform supports.


Prerequisites

Item Minimum version / requirement
WHMCS 8.0+ (with API access enabled)
Terraform 1.5+ (installed locally or via Terraform Cloud)
Git Any recent version
PHP 7.4+ (for the WHMCS hook)
Cloud provider DigitalOcean, Hetzner, AWS, etc. (we’ll use DigitalOcean in examples)
DNS provider Cloudflare (or any provider with a Terraform DNS plugin)
Terraform Cloud account Free tier is enough for small environments
Domain A test domain you control (e.g., example.dev)

You’ll also need a service account on the cloud provider with permission to create droplets, firewalls, and load balancers, plus an API token for the DNS provider.


High‑Level Architecture

Text
WHMCS Order → WHMCS Hook (PHP) → Terraform Cloud API → Terraform Run
    │                                          │
    └─► Stores order data in a JSON file      └─► Provisions:
                                                • VPS (DigitalOcean Droplet)
                                                • VPC / firewall rules
                                                • DNS A / CNAME records

The hook only pushes a small JSON payload to Terraform Cloud. All heavy lifting stays in Terraform, keeping your WHMCS server lightweight and secure.


1. Setting Up Terraform

Install Terraform locally (optional)

Bash
# macOS
brew install terraform

# Ubuntu/Debian
sudo apt-get install -y gnupg software-properties-common
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform

Create a Terraform Cloud workspace

  1. Sign in to https://app.terraform.io and create a new organization (or use an existing one).
  2. Click New Workspace, give it a name like whmcs-onboarding, and select Version ControlGitHub (or any VCS you prefer).
  3. Connect the repository you will push in the next steps.

Remote backend configuration

Add a backend.tf file to tell Terraform to store state in Terraform Cloud:

HCL
terraform {
  required_version = ">= 1.5"

  backend "remote" {
    organization = "your-org-name"

    workspaces {
      name = "whmcs-onboarding"
    }
  }
}

Commit this file to the repo; Terraform Cloud will automatically pick it up.


2. Writing the Provisioning Code

Below is a minimal but functional set of Terraform resources that creates a DigitalOcean droplet, attaches it to a VPC, opens SSH, and adds a DNS record in Cloudflare.

providers.tf

HCL
terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.28"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.13"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

provider "cloudflare" {
  api_token = var.cf_token
}

variables.tf

HCL
variable "do_token" {
  description = "DigitalOcean API token"
  type        = string
  sensitive   = true
}

variable "cf_token" {
  description = "Cloudflare API token"
  type        = string
  sensitive   = true
}

variable "order" {
  description = "JSON payload from WHMCS containing order details"
  type        = any
}

main.tf

HCL
# 1️⃣ Create a VPC (if you already have one, you can reference it instead)
resource "digitalocean_vpc" "client_vpc" {
  name   = "client-${var.order.client_id}"
  region = var.order.region
  ip_range = "10.${var.order.client_id}.0.0/16"
}

# 2️⃣ Provision the Droplet
resource "digitalocean_droplet" "client_vm" {
  name   = "client-${var.order.client_id}"
  region = var.order.region
  size   = var.order.plan_slug   # e.g., s-1vcpu-2gb
  image  = var.order.image_slug  # e.g., ubuntu-22-04-x64
  vpc_uuid = digitalocean_vpc.client_vpc.id

  ssh_keys = [var.order.ssh_key_id] # supplied by WHMCS or a default key
  tags = ["client-${var.order.client_id}"]
}

# 3️⃣ Open SSH and HTTP ports
resource "digitalocean_firewall" "client_fw" {
  name = "fw-client-${var.order.client_id}"
  droplet_ids = [digitalocean_droplet.client_vm.id]

  inbound_rule {
    protocol = "tcp"
    port_range = "22"
    source_addresses = ["0.0.0.0/0"]
  }

  inbound_rule {
    protocol = "tcp"
    port_range = "80"
    source_addresses = ["0.0.0.0/0"]
  }

  outbound_rule {
    protocol = "tcp"
    port_range = "0-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
}

# 4️⃣ DNS record in Cloudflare
resource "cloudflare_record" "a_record" {
  zone_id = var.order.cloudflare_zone_id
  name    = var.order.domain   # e.g., "app.example.dev"
  type    = "A"
  ttl     = 300
  value   = digitalocean_droplet.client_vm.ipv4_address
}

terraform.tfvars.example

HCL
do_token = "DIGITALOCEAN_API_TOKEN"
cf_token = "CLOUDFLARE_API_TOKEN"

Tip: Keep terraform.tfvars out of version control; use Terraform Cloud variables instead.


3. Connecting WHMCS to Terraform

WHMCS offers hooks – PHP functions that run on specific events. We'll use the OrderPaid hook to fire a POST request to Terraform Cloud’s Run API.

a. Create the hook file

Place the following file in modules/addons/whmcs-terraform/hooks/OrderPaid.php (or wherever your WHMCS installation loads custom hooks).

PHP
<?php
use WHMCS\Database\Capsule;

add_hook('OrderPaid', 1, function($vars) {
    // Load order details
    $orderId = $vars['orderid'];
    $order = Capsule::table('tblorders')
        ->where('id', $orderId)
        ->first();

    // Grab the first product (assume one product per order)
    $product = Capsule::table('tblhosting')
        ->where('orderid', $orderId)
        ->first();

    // Build a payload that matches the Terraform variable schema
    $payload = [
        "data" => [
            "attributes" => [
                "message" => "WHMCS order #{$orderId} - provisioning",
                "variables" => [
                    [
                        "key" => "order",
                        "value" => json_encode([
                            "client_id" => $order->userid,
                            "region" => "nyc3",
                            "plan_slug" => "s-1vcpu-2gb",
                            "image_slug" => "ubuntu-22-04-x64",
                            "ssh_key_id" => 123456, // optional, replace with your default
                            "domain" => $product->domain,
                            "cloudflare_zone_id" => "YOUR_ZONE_ID"
                        ]),
                        "sensitive" => true
                    ]
                ]
            ]
        ]
    ];

    // Terraform Cloud API endpoint
    $workspaceId = 'YOUR_WORKSPACE_ID';
    $url = "https://app.terraform.io/api/v2/workspaces/{$workspaceId}/runs";

    // Personal token with runs:create permission
    $apiToken = 'YOUR_TFC_API_TOKEN';

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Authorization: Bearer {$apiToken}",
        "Content-Type: application/vnd.api+json"
    ]);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpcode >= 200 && $httpcode < 300) {
        logActivity("Terraform run triggered for WHMCS order #{$orderId}");
    } else {
        logActivity("Terraform run failed (HTTP {$httpcode}) for order #{$orderId}: {$response}");
    }
});

What the hook does

  1. Pulls the order and product information from WHMCS tables.
  2. Builds a JSON payload that matches the order variable defined in variables.tf.
  3. Calls Terraform Cloud’s Run API, which creates a run, evaluates the plan, and applies it automatically (if auto‑apply is enabled).
  4. Logs success/failure to WHMCS activity log.

Security note: Store YOUR_TFC_API_TOKEN in WHMCS Configuration → System Settings → General Settings → Custom Admin Settings or an environment variable, never hard‑code it in the file.

b. Enable auto‑apply (optional)

In Terraform Cloud workspace settings, toggle Auto‑apply to On if you trust the incoming runs. For production you may want a manual approval step.


4. Managing State & Secrets Securely

Terraform Cloud already encrypts state at rest. For additional safety:

Concern Solution
API tokens Store as Sensitive Variables in the workspace UI.
Client‑specific data Use the sensitive = true flag in the variable definition, as shown in the hook.
Drift detection Enable Run Triggers that run terraform plan nightly and alert on drift.

If you prefer a self‑hosted backend, you can use an S3 bucket with DynamoDB locking, but that adds operational overhead.


5. Testing the End‑to‑End Flow

  1. Create a test product in WHMCS that maps to the VPS plan you defined (s-1vcpu-2gb).
  2. Set the product’s config options (e.g., region, image) as hidden fields – they will be read by the hook.
  3. Place an order with a test card (WHMCS has a built‑in “Free” gateway for sandbox).
  4. Observe:
    • WHMCS logs “Terraform run triggered…”
    • Terraform Cloud UI shows a new run, then an apply.
    • After a few minutes, the droplet appears in DigitalOcean, the firewall is attached, and the DNS record resolves.

If anything fails, check:

  • WHMCS activity log (hook errors).
  • Terraform Cloud run logs (detailed plan/apply output).
  • Cloud provider console for quota or permission issues.

6. Practical Checklist

✅ Item Done?
WHMCS OrderPaid hook installed and enabled
Terraform Cloud workspace created, linked to repo
backend.tf, providers.tf, variables.tf, main.tf committed
Sensitive variables (do_token, cf_token, TFC_API_TOKEN) stored in workspace
Cloud provider API token has create permissions for droplets, VPC, firewalls
DNS provider token can create A records for the target zone
Test product in WHMCS correctly maps to Terraform variables
Auto‑apply enabled (or manual approval workflow in place)
Monitoring/alerting on Terraform run failures (email, Slack, etc.)
Documentation for your team on how to roll back a run (e.g., terraform destroy)

Tick each box before you go live.


7. Comparison of Automation Approaches

Approach Pros Cons When to choose
WHMCS built‑in provisioning module (e.g., the DigitalOcean module) No extra code, UI integration Limited to provider‑specific features, hard to version control Small shops with a single provider
Custom PHP webhook → Terraform (this guide) Full IaC control, multi‑provider, audit trail Requires PHP coding, managing API tokens Medium‑to‑large providers needing flexibility
Server‑side orchestration (Ansible + WHMCS) Rich configuration management, idempotent Separate toolchain, longer run time Complex post‑provisioning steps (software stack)

Our Terraform‑centric method shines when you want infrastructure as code, repeatable runs, and the ability to switch clouds with minimal changes.


FAQ

1. Does this method work with providers other than DigitalOcean?
Yes. Replace the digitalocean_* resources with the equivalents from AWS, Hetzner, Linode, etc. The variable schema stays the same; only the provider block and resource types change.

2. What happens if a Terraform run fails after the droplet is created?
Terraform will attempt to roll back resources that support destroy (e.g., the droplet, firewall, DNS). If a step fails permanently, you’ll need to clean up manually or run terraform apply again after fixing the issue.

3. Can I provision multiple servers per order (e.g., a web + DB node)?
Absolutely. Extend the order JSON payload to include an array of server definitions and loop over them with for_each in Terraform. Example: for_each = var.order.servers.

4. How do I secure the WHMCS hook from being abused?

  • Only expose the hook internally (it runs on the WHMCS server).
  • Validate the order status (OrderPaid guarantees payment).
  • Store the Terraform Cloud token as a WHMCS configuration variable, not in source code.
  • Optionally, whitelist the IP ranges of your WHMCS server in Terraform Cloud’s API settings.

5. Is there a way to notify the client when provisioning finishes?
You can add a post‑run webhook in Terraform Cloud that calls the WHMCS API (ClientAddNote or TicketCreate). Alternatively, let the hook set a custom field on the order and use WHMCS’s built‑in email templates.


Conclusion

Automating WHMCS onboarding with Terraform turns a multi‑step, error‑prone process into a single, reproducible pipeline. By leveraging WHMCS hooks, Terraform Cloud, and provider APIs you get:

  • Speed – servers appear within minutes of payment.
  • Consistency – every environment is built from the same code base.
  • Visibility – Terraform’s plan/apply logs give you an audit trail.

Give the code in this post a spin on a sandbox, tweak the variables for your own provider, and you’ll quickly see the ROI in reduced support tickets and happier customers. For more real‑world automation patterns, check out the tutorials at mahbuburriad.com.

Happy provisioning!

Related

Related posts