Automating account provisioning in HestiaCP is essential when integrating hosting control planes with billing systems (WHMCS), internal ERP, or onboarding pipelines.
This article documents the exact operational flow I use in production: CLI validation first, API isolation with cURL, robust PHP implementation, and security controls to keep provisioning reliable and auditable.
1) How HestiaCP API actually works
HestiaCP API wraps backend CLI commands (v-*). You submit a POST request to the admin endpoint and pass command + ordered args.
Endpoint:
https://YOUR_SERVER:8083/api/
Core payload fields:
userpasswordhashcmdarg1 ... argN
2) CLI-first validation (mandatory)
Before coding integrations, validate command behavior directly on the server:
v-add-user new_user 'StrongPass123!' client@example.com default First Last
echo $?
If exit code is not 0, fix host-side issues first.
Useful checks:
which v-add-user
v-list-user admin json
v-list-packages json
3) Isolated API test with cURL
curl -k -X POST "https://your-server.com:8083/api/" \
-d "user=admin" \
-d "password=YOUR_PASSWORD" \
-d "hash=YOUR_HASH" \
-d "cmd=v-add-user" \
-d "arg1=new_user" \
-d "arg2=StrongPass123" \
-d "arg3=client@example.com" \
-d "arg4=default" \
-d "arg5=First" \
-d "arg6=Last"
Use -k only for controlled troubleshooting. In production, always enforce valid TLS.
4) Robust PHP implementation
I implemented requests with http_build_query() and strict cURL error handling:
<?php
$endpoint = 'https://your-server.com:8083/api/';
$payload = [
'user' => 'admin',
'password' => 'YOUR_PASSWORD',
'hash' => 'YOUR_HASH',
'cmd' => 'v-add-user',
'arg1' => 'new_user',
'arg2' => 'StrongPass123',
'arg3' => 'client@example.com',
'arg4' => 'default',
'arg5' => 'First',
'arg6' => 'Last',
];
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno) throw new RuntimeException("cURL error [$errno]: $error");
if ($httpCode < 200 || $httpCode >= 300) throw new RuntimeException("HTTP error [$httpCode]");
if ($response === false || trim($response) === '') throw new RuntimeException('Empty API response');
echo $response;
5) Common production failures and fixes
5.1 Invalid/expired hash
Fix: regenerate access key (User -> Access Keys or v-add-api-key) and rotate credentials.
5.2 Encoding/character corruption
Fix: always use http_build_query() and normalize input.
5.3 Port 8083 blocked
Fix: allow source IPs in firewall; validate with:
nc -vz your-server.com 8083
5.4 Invalid package or account limits
Fix:
v-list-packages json
v-list-user admin json
6) Security controls implemented
- Secrets stored outside source code.
- Redacted logs for password/hash.
- Valid TLS for panel hostname.
- Strict SSL verification in production.
- Source-IP ACL for admin API port.
- Correlation ID in every provisioning request log.
7) Post-deploy acceptance runbook
- create user via API
- create domain under that user
- validate resources in panel
- validate filesystem/service state
Checks used:
v-list-user new_user json
v-list-web-domains new_user json
v-list-dns-domains new_user json
8) Operational outcome
Provisioning moved from manual minutes to predictable API-driven seconds, with stronger observability and lower error rate.
Core principle that worked: validate locally (CLI), isolate remotely (cURL), then integrate safely (PHP + security hardening).
This post is licensed under CC BY-NC.
Comments
Join the discussion below.
Comments are not configured yet. Add Cusdis settings in /assets/json/config/blog-comments-config.json.