Skip to content

Automating cPanel Account Provisioning with the WHM API and Bash

Stop creating accounts by hand. This guide walks through scripting WHM account creation with the API, handling errors, and chaining post-provision tasks.

Written by AISali·July 28, 2026·4 min read
Automating cPanel Account Provisioning with the WHM API and Bash

If you're still clicking through WHM to create cPanel accounts one by one, you're leaving time and consistency on the table. Whether you're onboarding a batch of new clients, migrating from a billing platform, or building your own provisioning pipeline, scripting account creation through the WHM API is a foundational automation win.

This tutorial walks through the entire process: authenticating with the WHM API, creating accounts via createacct, handling common errors, and chaining post-provisioning tasks like DNS zone setup and package assignment. Everything here runs on a standard cPanel/WHM server with root access.

Prerequisites#

Before you start, make sure you have:

  • Root or reseller-level WHM access on the target server
  • A valid API token (preferred) or root password
  • curl and jq installed on your local machine or provisioning server
  • Basic familiarity with Bash

Generate a WHM API Token#

API tokens are more secure than passing your root password in every request. Create one in WHM:

  1. Navigate to Home → Development → Manage API Tokens
  2. Click Generate Token, give it a descriptive name like provisioning-script
  3. Copy the token immediately — WHM won't show it again

Store it somewhere safe, like a password manager or an environment variable on your automation server.

The Basic API Call#

WHM's API is accessible over HTTPS on port 2087. Here's the minimal structure for creating an account:

bash
#!/bin/bash

WHM_HOST="your-server.example.com"
API_TOKEN="your_api_token_here"

curl -sk \
  -H "Authorization: whm root:${API_TOKEN}" \
  "https://${WHM_HOST}:2087/json-api/createacct?username=newuser&domain=newuser.example.com&plan=default&[email protected]"

The -sk flag skips certificate verification — fine for internal scripts, but in production you should pin or validate your server's certificate.

Key Parameters for createacct#

ParameterRequiredDescription
usernameYescPanel username, 8 chars max, alphanumeric
domainYesPrimary domain for the account
planYesWHM package name
contactemailRecommendedAccount owner's email
passwordAuto-generated if omittedAccount password
ipNoDedicated IP (defaults to shared)
cgiNo1 to enable CGI (default), 0 to disable

Parsing the Response#

WHM returns JSON. The critical field is metadata.result1 means success, 0 means failure. Always check it.

bash
response=$(curl -sk \
  -H "Authorization: whm root:${API_TOKEN}" \
  "https://${WHM_HOST}:2087/json-api/createacct?username=${USERNAME}&domain=${DOMAIN}&plan=${PLAN}&contactemail=${EMAIL}")

result=$(echo "$response" | jq -r '.metadata.result')
reason=$(echo "$response" | jq -r '.metadata.reason')

if [ "$result" -eq 1 ]; then
  echo "Account created successfully for ${DOMAIN}"
else
  echo "Failed: ${reason}"
  exit 1
fi

Common failure reasons include duplicate usernames, domain already existing on the server, or exceeding the reseller's account limit. The reason field gives you a human-readable explanation.

A Complete Provisioning Script#

Here's a more production-ready version that accepts arguments and handles post-creation tasks:

bash
#!/bin/bash
set -euo pipefail

WHM_HOST="${WHM_HOST:?Set WHM_HOST}"
API_TOKEN="${WHM_API_TOKEN:?Set WHM_API_TOKEN}"

USERNAME="$1"
DOMAIN="$2"
PLAN="${3:-default}"
EMAIL="$4"

API_BASE="https://${WHM_HOST}:2087/json-api"
AUTH_HEADER="Authorization: whm root:${API_TOKEN}"

# Validate username length
if [ ${#USERNAME} -gt 8 ]; then
  echo "Error: Username must be 8 characters or fewer."
  exit 1
fi

# Create the account
response=$(curl -sk -H "$AUTH_HEADER" \
  "${API_BASE}/createacct?username=${USERNAME}&domain=${DOMAIN}&plan=${PLAN}&contactemail=${EMAIL}")

result=$(echo "$response" | jq -r '.metadata.result')
reason=$(echo "$response" | jq -r '.metadata.reason // empty')

if [ "$result" != "1" ]; then
  echo "Provisioning failed: ${reason}"
  exit 1
fi

# Post-provisioning: set up DKIM and SPF
curl -sk -H "$AUTH_HEADER" \
  "${API_BASE}/cpanel?cpanel_jsonapi_user=${USERNAME}&cpanel_jsonapi_module=Email&cpanel_jsonapi_func=add_dkim&cpanel_jsonapi_apiversion=3" > /dev/null

curl -sk -H "$AUTH_HEADER" \
  "${API_BASE}/cpanel?cpanel_jsonapi_user=${USERNAME}&cpanel_jsonapi_module=Email&cpanel_jsonapi_func=add_spf&cpanel_jsonapi_apiversion=3" > /dev/null

echo "Account ${USERNAME} (${DOMAIN}) provisioned with DKIM and SPF."

Save this as provision.sh, make it executable, and call it:

bash
./provision.sh johndoe johndoe.com business [email protected]

Batching Accounts from a CSV#

If you're migrating clients or onboarding in bulk, pipe a CSV through a loop:

csv
username,domain,plan,email
alice,alice-store.com,business,[email protected]
bob,bobdesigns.io,starter,[email protected]
charlie,charlie.dev,business,[email protected]
bash
tail -n +2 accounts.csv | while IFS=',' read -r username domain plan email; do
  ./provision.sh "$username" "$domain" "$plan" "$email"
  sleep 2  # avoid hammering the API
  echo "---"
done

The sleep 2 is intentional — WHM can behave unpredictably under rapid-fire account creation, especially with DNS zone generation.

Error Handling and Logging#

For anything beyond one-off use, log every provisioning attempt:

bash
LOG_FILE="/var/log/provisioning.log"

echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | ${USERNAME} | ${DOMAIN} | result=${result} | ${reason}" >> "$LOG_FILE"

If you're running this on a Salieno Core instance, the same API patterns apply — WHM-compatible endpoints mean your provisioning scripts don't need to change when you move between cPanel-based setups.

What to Do Next#

Once account provisioning is automated, the natural next steps are:

  • Hook it into your billing system — trigger provisioning on payment confirmation via webhooks
  • Add suspension/unsuspension scripts using the suspendacct and unsuspendacct API calls
  • Monitor account limits with resellerstats to avoid hitting your allocation
  • Automate DNS glue records if you're using private nameservers

Manual provisioning doesn't scale. A 30-line Bash script that you've tested once will be more reliable than the 300th time you've clicked through WHM by hand. Start with the single-account call, validate it works, then layer on batching and logging. The WHM API is well-documented and stable — there's no reason to provision accounts manually in 2025.

Share

0 comments

Loading comments…

More from the blog