On this page
Why Automate WHMCS Onboarding?
Every time a client clicks Buy in WHMCS you (or your support team) have to:
- Spin up a new VPS or virtual machine.
- Attach it to the right VLAN or private network.
- 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
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)
# 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
- Sign in to https://app.terraform.io and create a new organization (or use an existing one).
- Click New Workspace, give it a name like
whmcs-onboarding, and select Version Control → GitHub (or any VCS you prefer). - 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:
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
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
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
# 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
do_token = "DIGITALOCEAN_API_TOKEN"
cf_token = "CLOUDFLARE_API_TOKEN"
Tip: Keep
terraform.tfvarsout 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
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
- Pulls the order and product information from WHMCS tables.
- Builds a JSON payload that matches the
ordervariable defined invariables.tf. - Calls Terraform Cloud’s Run API, which creates a run, evaluates the plan, and applies it automatically (if auto‑apply is enabled).
- Logs success/failure to WHMCS activity log.
Security note: Store
YOUR_TFC_API_TOKENin 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
- Create a test product in WHMCS that maps to the VPS plan you defined (
s-1vcpu-2gb). - Set the product’s config options (e.g., region, image) as hidden fields – they will be read by the hook.
- Place an order with a test card (WHMCS has a built‑in “Free” gateway for sandbox).
- 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 (
OrderPaidguarantees 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!