Skip to content

Server connection & credentials

How a panel declares its Server connection form and account-credential rules in the manifest, and the helper trait every driver uses to make TLS-aware, well-masked API calls.

8 min readUpdated Aug 15, 2026
On this page

A panel has to connect to real servers, and Core has to collect the right credentials to do it — a WHM API token, a Plesk admin login, a self-signed-cert toggle. None of that requires a Core edit. Two blocks in your salieno.jsonconnection and credentials — tell Core how to render the Server form and how to pre-generate account credentials, and a helper trait gives your driver a TLS-aware HTTP client so every call behaves consistently. This article covers all three.

The connection block builds the Server form#

When an operator adds a server under a group that uses your panel, Core renders the Server form from your manifest's connection block. There is no per-panel form in Core; the fields, labels, help text and documentation link all come from what you declare.

json
"connection": {
    "label": "cPanel / WHM",
    "port": "2087",
    "auth": "token",
    "username_label": "WHM username (root or reseller)",
    "password_label": "WHM password",
    "token_label": "WHM API token",
    "token_help": "Create in WHM » Development » Manage API Tokens. Sent as \"Authorization: whm user:token\" — the token is the credential; the password is not used for API calls.",
    "docs": "https://api.docs.cpanel.net/whm/tokens/"
}
FieldWhat it does
labelThe panel's display name on the connection section of the Server form.
portPre-fills the API port field. Match your driver's default port (see below).
authWhich credential fields the form shows: "token", "password", or "token-or-password".
username_labelLabel for the username field — the operator's mental model differs per panel (WHM "username" vs Plesk "Admin login").
password_labelLabel for the password field.
token_labelLabel for the API-token / key field.
token_helpInline help under the token field: how to create the token, and how it is used.
docsAn external link to the panel vendor's API docs, shown alongside the form.

The three auth modes#

auth decides which credential inputs Core presents:

  • "token" — the panel authenticates with an API token; that is the credential. cPanel/WHM uses this: the token is sent in an Authorization header and the password is not used for API calls.
  • "password" — the panel authenticates with a username + password only.
  • "token-or-password" — the panel accepts either. Plesk uses this: a secret API key when one is configured, otherwise the admin login + password. Declaring this mode shows both, and the token field is presented as optional.

Choose the mode your driver actually implements. Your driver reads whichever of $server->api_token / $server->password is populated and builds the request accordingly — see the Plesk snippet below, which branches on exactly that.

The connection block is purely declarative and is read straight from the manifest, so a new panel onboards with no Core changes. For the full manifest schema see The manifest.

What the driver receives: the $server object#

Whatever the operator enters on the form is stored on the server record and handed to your driver as $server. These are the fields you read:

FieldMeaning
$server->hostnameProtocol + host + port, as the admin entered it (e.g. https://host:2087).
$server->usernameThe panel login (root/reseller for WHM, admin for Plesk).
$server->passwordThe panel password (may be empty when a token is used).
$server->api_tokenThe API token / secret key (may be empty when password auth is used).
$server->verify_sslThe per-server TLS-verify toggle. Control panels often use self-signed certs.
$server->ip_addressThe server's IP, when the operator set one.
$server->ns1$server->ns4Default nameservers configured for the server.
$server->idThe server record id (useful for admin notifications / logging).

Never write any of these back into your panel's own installed folder — the trust model hashes every on-disk file, so a self-modifying panel breaks its own integrity check. Keep all state in what Core passes you ($server, $hosting) and in the panel server itself. See Security & trust model.

The credentials block: pre-generated account credentials#

Separate from server connection is account credential generation. When Core provisions a new hosting account, it pre-generates a username and password before calling your create(). Different panels enforce different limits — cPanel usernames max at 8 characters, Plesk at 16 — so Core needs to know the ceiling to generate a value the panel will accept.

json
"credentials": {
    "username_max_length": 8
}

Core uses username_max_length to cap the generated username so it fits your panel's rules. Your create() then receives those pre-generated values on $hosting->username / $hosting->password.

Two things follow from this:

  1. Echo the real credentials back in `create()`'s `data`. The data.password you return is stored on the service and emailed to the customer. If the panel accepted the pre-generated password, echo it back; if the panel forced a different value, return that instead. Omitting a key keeps Core's pre-generated value.
  2. Validate before you send. Both example drivers call validateHostingInput($domain, $username) first, which enforces strict domain and username rules. The generated credentials should already pass, but validating guards against a hand-edited service.

The exact create() return shape is documented in The driver contract.

Making authenticated API calls#

Extend AbstractPanel and you inherit HostingModuleTrait, which gives you three helpers for connecting. Route every request through them so TLS handling, timeouts and error masking are consistent across all panels.

panelBaseUrl($server, $defaultPort, $forceHttps = true)#

Builds a clean API base URL from $server->hostname, normalising the scheme and ensuring the port is present:

php
protected function whmBaseUrl($server): string
{
    // WHM's API on :2087 is HTTPS-only — an http:// request 301-redirects to an
    // HTML login page and yields a null/invalid response. Force https + ensure the port.
    return $this->panelBaseUrl($server, 2087, true);
}

$defaultPort is appended only when the stored hostname has no :port. Keep it equal to the manifest's connection.port. $forceHttps defaults to true because control-panel APIs on their SSL ports are HTTPS-only; leaving it on means an http:// a user typed by mistake is upgraded rather than followed into a redirect.

panelHttp($server, $headers = [], $timeout = 30)#

Returns a pre-configured Laravel HTTP client. It:

  • applies the per-server verify_ssl toggle (so self-signed certs work when the operator opted in),
  • sets sane connect + request timeouts,
  • sends a stable, identifiable User-Agent (many panels sit behind a WAF that blocks the bare Guzzle UA and serves an HTML challenge instead of JSON),
  • and requests Accept: application/json by default.

Issue all API calls through it. A WHM GET, token-authenticated:

php
$base = $this->panelBaseUrl($server, 2087, true);
$auth = 'whm ' . $server->username . ':' . $server->api_token;

$response = $this->panelHttp($server, ['Authorization' => $auth])
    ->get($base . '/json-api/listpkgs', ['api.version' => 1]);

$data = $this->decodeJsonResponse($response); // null on a non-JSON body

The Plesk driver shows the token-or-password branch — a secret key uses a single KEY header, otherwise Plesk's dedicated login/password headers (not HTTP Basic):

php
$headers = ['Accept' => 'application/xml'];
if (! empty($server->api_token)) {
    $headers['KEY'] = (string) $server->api_token;            // secret key
} else {
    $headers['HTTP_AUTH_LOGIN']  = (string) $server->username; // login + password
    $headers['HTTP_AUTH_PASSWD'] = (string) $server->password;
}

$response = $this->panelHttp($server, $headers)
    ->withBody($packet, 'text/xml')
    ->post($this->panelBaseUrl($server, 8443, true) . '/enterprise/control/agent.php');

Note that credential-bearing calls (account creation, password changes) should travel in the request body via ->asForm()->post(...) or ->withBody(...)->post(...), not the query string, so secrets are not written to the panel's access log or a fronting proxy.

decodeJsonResponse($response)#

A tolerant JSON decode that returns null when the body is empty or not JSON — for example an HTML login page returned by a redirect or an auth failure. Use it so you can report a clear connection error instead of mis-parsing HTML as "invalid response".

Turning failures into operator guidance#

sanitizeErrorMessage(?string $message) does two jobs, and you should wrap every raw error string with it before returning it:

  1. Translates low-level failures into actionable guidance. A raw cURL error 28 tells an operator nothing. The helper detects SSL/certificate, timeout, DNS-resolution and connection-refused failures and returns a concrete next step — e.g. a TLS error suggests disabling certificate verification for that server; a timeout points at the hostname, API port, and the panel/firewall's IP allowlist.
  2. Masks sensitive data. It strips IPv4/IPv6 addresses, absolute file paths, and anything resembling an auth header or token, then caps the length — so a panel's verbose error never leaks infrastructure detail into the UI or logs.
php
} catch (\Exception $error) {
    return ['success' => false, 'message' => $this->sanitizeErrorMessage($error->getMessage())];
}

Beyond transport errors, map your panel's own API error codes to plain guidance too. The Plesk driver translates codes like 1015 ("service plan not found") and 1017 ("the API account is not permitted to perform this operation") into sentences an operator can act on, then still passes the result through sanitizeErrorMessage. When a call fails on an admin-facing action, secureAdminNotification($hosting, $message, $url) raises a masked admin notification so the failure surfaces where the operator will see it.

Reachability and "Test connection"#

loginServer($server) doubles as the connection health check: the Server form's "Test connection" button invokes it. Implement it to make one lightweight, credential-verifying call first (WHM uses gethostname, Plesk a server/get), and only then mint an SSO URL. If credentials are wrong or the panel is unreachable, return ['success' => false, 'message' => ...] with a sanitized message so the operator learns what to fix. This ties the connection block, the credentials, and your API client together into one testable path — see Testing your panel.

panelsconnectioncredentials
Was this article helpful?
Still stuck?Contact support
Server connection & credentials · Salieno Docs