Silo WHMCS Provisioning Module connects WHMCS service lifecycle events to the Silo admin API. It creates users, suspends and restores access, updates plan attributes, resets passwords, and gives staff a direct status view from the WHMCS service page.
- Create Silo users when WHMCS provisions a service.
- Suspend and unsuspend by toggling the Silo user's
enabledstate. Terminate deletes the Silo user by default (configurable) — see Service Lifecycle. - Reconcile Silo attributes from WHMCS product settings and configurable options.
- Keep Silo email and username aligned with WHMCS.
- Link each WHMCS service to Silo by stored user ID, with email and username fallback recovery.
- Generated usernames by default, or optional customer-chosen usernames at order time.
- Validate customer-chosen usernames for format, reserved names, blocked words, and uniqueness.
- Auto-create the internal custom fields it needs — no manual setup.
- Scaffold a ready-to-price configurable-option group with one click.
- Test Connection button on the Servers page.
- Every Silo API call recorded in the WHMCS Module Log with secrets masked.
- Show Silo status in the WHMCS admin service tab, and a customer-facing client-area panel (plan, profiles, live streams) with a self-service "reset password & sign out everywhere" action.
- WHMCS daily cron hook that logs drift across every attribute (limits, libraries, downloads, playback quality, enabled state).
- WHMCS 8.x.
- PHP 8.1 or newer.
- PHP JSON extension.
- PHP cURL extension, or
allow_url_fopenenabled for HTTPS streams. - Outbound HTTPS access from WHMCS to the Silo server.
- A Silo admin API key.
Composer is not required. The module is plain PHP and ships with its own small autoload file.
Download the release archive named like:
silo-whmcs-module-vX.Y.Z.tar.gzExtract it into the WHMCS server modules directory as silo:
mkdir -p /path/to/whmcs/modules/servers/silo
tar -xzf silo-whmcs-module-vX.Y.Z.tar.gz -C /path/to/whmcs/modules/servers/siloThe final path should contain:
/path/to/whmcs/modules/servers/silo/silo.php
/path/to/whmcs/modules/servers/silo/hooks.php
/path/to/whmcs/modules/servers/silo/autoload.php
In Silo, create an admin API key.
In WHMCS, go to System Settings -> Servers -> Add New Server and create a
server with these values:
| Field | Value |
|---|---|
| Name | Any descriptive name, for example Silo |
| Hostname | Your Silo hostname, without https:// |
| Port | Silo's port. Leave at 443 (Secure) / 80 for the default. |
| Secure | Enabled for HTTPS |
| Username | Unused; any value is acceptable |
| Password / Access Hash | Silo admin API key |
| Module | silo |
The hostname field is tolerant: a leading https://, a trailing path, or an
embedded :port are normalized, and a non-default Port is honored (in
provisioning, the daily cron, and the client-edit sync alike).
After saving, use the Test Connection button on the Servers page to verify the hostname, port, TLS, and API key in one step. A failure message distinguishes an unreachable host from an authentication error.
For each WHMCS product that should provision Silo accounts, open:
System Settings -> Products/Services -> Edit Product -> Module Settings
Set the module name to silo, then configure:
Every Silo user is provisioned with the user role — the module
sells no admin accounts, so role is fixed and not a configurable field.
| Field | Description |
|---|---|
| Library IDs | Comma-separated Silo library IDs, e.g. 1,3,5. Leave blank to grant all libraries (including any added to Silo later). |
| Max concurrent streams | Integer stream limit. |
| Max concurrent transcodes | Integer transcode limit. |
| Max profiles | Integer profile limit. Forced to a minimum of 1 (Silo rejects 0). |
| Downloads allowed | Whether downloads are allowed. |
| Download transcode allowed | Defaults to No. Forced off whenever Downloads allowed is off. |
| Max playback quality | Blank for unrestricted, or 1080p, or 4k. Silo only enforces these three; legacy 720p/480p behave as 1080p. |
| Create default profile on CreateAccount | Recommended: enabled. Silo creates one ready-to-use viewing profile inside the new account; if it can't, Silo rolls back the user. With it off, the customer logs in to an empty account and must create a profile. |
| Allow customer-chosen username | See Username Behavior. |
| Delete Silo user on termination | Default ON. ON: terminating the service permanently deletes the Silo user (profiles + watch history; cannot be undone). OFF: termination only disables the user (data retained; a re-order re-links only if it resolves to the same Silo server — see Service Lifecycle). |
| Re-home returning customers (multi-server) | Default OFF. ON: a new order whose user already exists on another configured Silo server moves the service to that server and re-links the existing user instead of creating a fresh account. See Re-home returning customers. |
| Allow client-area password reset | Default ON. ON: customers can use the client-area reset action; the generated password is shown once and written to the WHMCS service. OFF: only staff can reset passwords from the admin service page. |
| Client reset cooldown (seconds) | Default 60. Minimum seconds between client-area password resets for one service. Each reset signs the customer out everywhere, so this stops the button being spammed into a lockout. Set 0 to disable the cooldown. |
The module auto-creates the service custom fields it needs, so no manual setup is required. All three are created internal admin-only and not on the order form:
| Field Name | Purpose | On order form? |
|---|---|---|
silo_user_id |
Silo linkage — written by the module | Never |
silo_library_names_cache |
Library-name cache — written by the module | Never |
desired_username|Enter your desired username |
Chosen username; blank → auto-generated | No by default — an admin enables it manually (see Username Behavior) |
They are created the first time the module provisions or reconciles a service
on the product, and immediately for every silo product when the
Scaffold Configurable Options admin button is used (so a product with no
service yet — e.g. before its first order — is fully prepped). Creation is
create-if-missing only: once a field exists the module never changes it, so
any edit you make (including ticking Show on Order Form for
desired_username) is never undone. No field is ever WHMCS-"Required".
The module reads normal WHMCS configurable options by name, so admins use
WHMCS-native checkboxes, dropdowns, radio buttons, and quantity fields. Name
matching is case-insensitive and any punctuation/separators are treated as
spaces — so Library IDs, library-ids, and library_ids all match, but a
run-together LibraryIDs (no separator) does not.
On any Silo-backed service, the Scaffold Configurable Options admin
button creates a Silo Options group with a curated starter set —
Extra Streams, Extra Transcodes, Extra Profiles (quantity),
4K Streaming, Downloads Allowed, Download Transcode Allowed (yes/no),
Max Playback Quality (dropdown), plus a Library N opt-in
checkbox for each live Silo library — with 0.00 pricing in every
currency, and links it to every silo product. (It does not create every
recognized name below — e.g. the Max Streams/Max Transcodes/Max Profiles
overrides and the Library IDs/Libraries dropdowns are left for you to add
if wanted.) It is idempotent: existing options and any prices you set are never
overwritten. You only need to set prices, then the group is live. (Configurable
options are intentionally not auto-created on lifecycle hooks — they are
optional upsells whose pricing the module must not invent.)
| Configurable Option Name | WHMCS Control | Behavior |
|---|---|---|
Extra Streams |
Quantity or dropdown | Adds the selected number to Max concurrent streams. |
Max Streams |
Quantity or dropdown | Replaces Max concurrent streams. |
Extra Transcodes |
Quantity or dropdown | Adds to Max concurrent transcodes. |
Max Transcodes |
Quantity or dropdown | Replaces Max concurrent transcodes. |
Extra Profiles |
Quantity or dropdown | Adds to Max profiles. |
Max Profiles |
Quantity or dropdown | Replaces Max profiles. |
Downloads Allowed |
Checkbox or dropdown | Overrides download access. |
Download Transcode Allowed |
Checkbox or dropdown | Overrides download-transcode access (still forced off if downloads are off). |
Max Playback Quality |
Dropdown or radio | Sets playback quality. Silo enforces only unrestricted, 1080p, or 4k; 720p/480p map to 1080p. |
4K Streaming |
Checkbox | When checked, sets playback quality to 4k. |
Library IDs |
Dropdown / radio / quantity | Adds every number found in the selected value as a library ID. Dropdown/radio is single-select, so one choice only contributes the IDs in that one value (e.g. a sub-option labelled 3,5 → libraries 3 and 5; Movies (3) → library 3). |
Libraries |
Dropdown / radio / quantity | Same as Library IDs (aliases: Library Access, Library Pack). |
Library 3 |
Checkbox | When checked, appends library ID 3. Use these for per-library opt-in — one checkbox per library lets customers enable libraries individually. |
Library ID 3 |
Checkbox | Same as Library 3. |
Library access is all libraries unless something is listed. If the
product's Library IDs field is blank and no library configurable option
contributes an ID, the customer gets every library (including ones added to
Silo later). As soon as any ID is listed — product field or configurable
option — access is restricted to exactly that set.
Examples:
- Checkbox
4K Streaming=Yes: setsmax_playback_qualityto4k. - Quantity
Extra Streams=2: adds two streams to the product base. - Checkbox
Library 7=Yes: adds library7(the per-library opt-in pattern). - Dropdown
Librarieswith a sub-option labelledMovies & Anime (3,5)selected: adds libraries3and5(the digits in that one chosen value).
To let customers pick libraries individually, create one Library N checkbox
per library — a single dropdown/radio can only select one value. Caveat: for
Library IDs/Libraries, any digits in the chosen value are treated as
library IDs, so don't put unrelated numbers in those option/value labels.
Values like No, Off, False, 0, None, and empty strings are disabled.
By default the module generates usernames in the form abcd123: four lowercase
letters followed by three digits, retrying up to five times on collision.
The module auto-creates the field as
desired_username|Enter your desired username (no spaces around the | — WHMCS
does not trim it) — admin-only and not on the order form by default. To collect it from customers, an admin ticks
Show on Order Form on that field (and enables Allow customer-chosen
username so the module consults it). The module never moves the field on or
off the form, so that manual choice is never reverted.
The field is found by logical name regardless of its WHMCS label, so you may
rename or relabel it freely. (WHMCS keys customfields by the text after a
| in the field name — e.g. Enter your desired username — not by
desired_username; the module resolves it tolerantly so this does not break
the feature.)
When Allow customer-chosen username is enabled, a non-empty
desired_username (set by the customer at order, or by an admin on the
service) is validated:
- 3 to 12 characters.
- Lowercase letters, digits, underscores, and hyphens only.
- Not a reserved system name.
- Not present in the blocked-word list.
- Not already used by another Silo user.
If it is blank or invalid handling falls back appropriately (blank → a username is generated; invalid → the order/provision reports the validation error).
To override the blocked-word list, place a bad_words.txt file next to
silo.php in the installed module directory. The override replaces the
default list in data/bad_words.default.txt.
| WHMCS action | Silo effect |
|---|---|
| CreateAccount | Creates the user (or re-attaches an existing one), applies all attributes, optionally creates a default profile. |
| SuspendAccount | Sets the Silo user enabled = false. |
| UnsuspendAccount | Sets enabled = true. |
| TerminateAccount | Default: deletes the Silo user (profiles + watch history). If "Delete Silo user on termination" is OFF, only sets enabled = false and retains everything. |
| ChangePackage / Upgrade / Reconcile | Re-applies all product + configurable-option attributes to Silo. |
| ChangePassword / Reset Password | Updates the Silo user's password. Silo revokes all of that user's sessions on a password change, so this also signs them out everywhere. |
Termination is governed by the Delete Silo user on termination product option (default ON):
- ON (default): the Silo user is permanently deleted on terminate — profiles and watch history included. This cannot be undone. A user that is already gone (HTTP 404) is treated as success; if no user is linked, termination still succeeds.
- OFF: terminate only disables the user (functionally identical to
Suspend). The account and history are retained, and a returning customer is
re-linked and re-enabled on the next CreateAccount or Reconcile —
provided it resolves to the same Silo server. Linkage resolution is
per-server (see below): reactivating the same WHMCS service always works,
because it keeps the
silo_user_idlinkage and server assignment; a brand-new order is matched only by email/username, so in a multi-server group it can be routed to a different server where the disabled user does not exist — there a fresh account is created and the old history is not recovered. To rely on retention with multiple servers, either enable Re-home returning customers (below), pin re-orders back to the originating server (specific-server / one-server-per-product), or treat returns as reactivations rather than new orders. Removal then requires a deliberate manual action in Silo — a data-retention/GDPR consideration.
Suspend and Unsuspend never delete, regardless of this option.
The Re-home returning customers product option (auto_rehome_on_reorder,
default OFF) closes the multi-server gap above. When ON, a brand-new order
whose user can't be resolved on the assigned server triggers a cross-server
lookup (every active silo-typed WHMCS server, by email then username,
cached in a mod_silo_home pointer table). If the customer's existing
Silo user is found on another server, the WHMCS service is moved to
that server and the existing user is re-linked and re-enabled — preserving
profiles and watch history — instead of creating a fresh account. The move
tries WHMCS's UpdateClientProduct (serverid) first, then verifies it and
falls back to a direct tblhosting write if that WHMCS version ignores the
parameter — so it works regardless of version.
- A genuine new customer (found nowhere) is created normally; the chosen home is recorded so future re-orders are deterministic and cheap.
- If a home exists but the move fails, provisioning returns a descriptive error rather than silently creating a fresh account and orphaning history.
- Users only reachable on a disabled server fall back to a fresh account.
- No effect with a single server or a shared Silo backend.
The home pointer is kept in sync with reality automatically: a terminate
with "Delete Silo user on termination" ON drops the pointer (no
account left to re-home to), and a WHMCS ClientEdit email rename moves
the pointer to the new email key (so multi-server re-home keeps probing
the right server for that customer). If WHMCS's UpdateClientProduct
ignores serverid on your version and the module falls back to a direct
tblhosting.server write, that fallback is logged to the activity log so
the audit trail explains what re-pointed the service.
Each WHMCS service is linked to Silo through three signals, checked in order:
silo_user_idservice custom field — verified: the returned user's email must match the WHMCS client email (case-insensitive). A stale or hand-edited ID pointing at a different customer falls through to tier 2 instead of letting subsequent writes (rename, password reset, suspend) hit the wrong account. Silo responses that omit the email field are treated as unverifiable and accepted.- WHMCS client email, lowercased.
- WHMCS service username.
When a hook finds a user through a fallback signal it repairs
silo_user_id. Successful updates also push the WHMCS email and service
username back to Silo, making WHMCS the source of truth for those fields.
Failure handling differs by handler intent:
- Display / idempotent paths (
ClientArea,AdminServicesTab,SetEnabled,ChangePackage, etc.) treat a Silo API error during fallback resolution as "unresolved" rather than crashing the hook — the page or action degrades gracefully. CreateAccountuses strict resolution: if Silo is unreachable while scanning for an existing user, the hook refuses to provision rather than risk creating a duplicate. WHMCS retries the hook once Silo recovers. Additionally, ifcreateUseritself fails with a network/5xx error, the module looks the user up by email post-failure and links to the recovered record — so a "response lost in flight" network blip can never produce two Silo accounts for one order.
Resolution is per-server: every hook runs against the one Silo server WHMCS assigned to that service, and the fallbacks (email/username) only search that server's users — there is no cross-server lookup. This is why retain-on-terminate recovery depends on a re-order landing back on the originating server (see Service Lifecycle → OFF above).
On a Silo-backed WHMCS service, staff can use:
Silo statustab: Silo user ID, username, email, enabled state, role, libraries (or "All libraries (unrestricted)"), stream / transcode / profile limits, downloads + download-transcode access, max playback quality, last seen, and an admin deep link — full parity with the customer-facing client area so a reconcile can be visually verified without leaving WHMCS.Reconcile from WHMCS: pushes the current WHMCS product and configurable option state to Silo (also ensures custom fields exist).Reset Password: generates a strong password, updates Silo, and writes it back to the WHMCS service password (WHMCS-encrypted). Also signs the customer out of all devices (Silo revokes sessions on a password change). Customers can self-serve the same action from the client area when Allow client-area password reset is ON.Scaffold Configurable Options: see Configurable Options.
The module renders templates/clientarea.tpl for the customer service page:
- Status & identity — active/suspended badge, Silo username, member-since.
- Plan — concurrent streams, transcodes, profile limit, playback quality, download access.
- Live usage — profiles used vs. limit (with names), and "watching now"
(active streams vs. limit, with titles). Stream/profile counts come from
/admin/sessionsand/admin/users/{id}/profiles;client_ipand other PII are never surfaced — only titles/counts. - Libraries — names ("All libraries" when unrestricted), last-seen time, and a sign-in link.
Self-service: a Reset password & sign out all devices button when Allow client-area password reset is ON (default). A Silo admin password change also revokes every session server-side, so this one action both rotates the password and signs the customer out everywhere. The generated password is shown once in WHMCS and written to the service password; turn the option OFF if staff should handle resets instead. The button is rate-limited per service (see Client reset cooldown (seconds), default 60) so it can't be spammed into a sign-out loop — a too-soon retry returns a "please wait" message without touching Silo.
Each enrichment (getUser, profiles, sessions) degrades independently —
if one Silo call fails the rest of the page still renders. All output is
escaped; configuration problems show a generic message (details go to the
activity log, never to the customer). Note this adds up to ~3 admin-API calls
per page view (library names stay cached 24h via the custom field).
Every Silo API call is recorded in WHMCS → Utilities → Logs → Module Log (method, URL, status, body). The admin API key and any password in a payload are passed to WHMCS as mask values, so they are redacted in the log. This makes opaque failures (wrong port, TLS, auth, CDN/WAF responses) diagnosable without extra tooling.
hooks.php registers a DailyCronJob hook that scans active services on
every silo-typed server and reports drift to the WHMCS activity log.
The check covers every attribute the module manages — enabled, role,
library_ids, max_streams, max_transcodes, max_profiles,
download_allowed, download_transcode_allowed, and
max_playback_quality — by rebuilding the expected state from the
service's product config + configurable options and comparing it against
Silo's observed user record.
The cron runs unconditionally on every Silo-typed server (there is
no per-server opt-out today). This is logging only; it does not fix
drift. Use Reconcile from WHMCS on the affected service to correct it.
Normally auto-created. If you see this, run Reconcile from WHMCS or
Scaffold Configurable Options on a service of that product, or add
silo_user_id and silo_library_names_cache by hand.
The Silo user may have been deleted, or all linkage signals changed
outside WHMCS. Recreate/correct the Silo user so the email or username
matches WHMCS, then run Reconcile from WHMCS. If you know the Silo user
ID, set silo_user_id on the service and reconcile.
Check the Module Log entry for the exact URL/response. 401/403 = API key;
404 from a CDN/WAF often means an HTML error page rather than the API; 5xx
= Silo server. Also verify the configured hostname, port, and API key
(the Test Connection button checks all of these).
Choose another. For generated usernames the module retries collisions up to five times.
The module has no Composer build step. Create a release archive from a clean checkout:
mkdir -p dist
tar \
--exclude='./.git' \
--exclude='./dist' \
--exclude='./.gitignore' \
--exclude='./.claude' \
--exclude='./.env' \
--exclude='./.env.*' \
--exclude='./bad_words.txt' \
--exclude='./.phpunit.result.cache' \
-czf dist/silo-whmcs-module-vX.Y.Z.tar.gz .Upload the resulting archive as the GitHub release asset.
The shipped module is dependency-free — Composer and PHPUnit are dev
tooling only (vendor/ is gitignored and never part of the release
archive). Basic syntax validation:
find . -path './.git' -prune -o -path './dist' -prune -o -name '*.php' -print -exec php -l {} \;composer install # one-time; pulls PHPUnit into vendor/ (dev only)
composer test # or: vendor/bin/phpunitTests run with no WHMCS or Silo install: tests/Support/ shims the
WHMCS runtime (localAPI, Capsule, logActivity, …) and a
FakeClient stands in for the Silo HTTP API, so handlers exercise
the real Identity/AttributeMapper/CustomFieldStore code. Coverage
spans every service-lifecycle and admin handler (Create/Change/
Terminate/SetEnabled, ChangePassword, AdminResetPassword,
ClientResetPassword, TestConnection, ClientArea, AdminServicesTab,
ScaffoldOptions, DailyReconciler) and the pure logic
(Identity, AttributeMapper, ProductConfig/ServerConfig, PlaybackQuality,
Username validation/generation, BadWordList, DriftCheck, HomeStore,
ConfigOptionScaffolder) — including the status-aware enabled assertion
in CreateAccount/ChangePackage, the tier-1 identity verification, and
the createUser idempotency recovery on transient errors.
CI runs the suite on every push and PR (.github/workflows/tests.yml,
PHP 8.2–8.4) plus a php -l lint at the 8.1 runtime floor.
Release validation should still include a staging WHMCS install before
publishing the packaged archive — run
docs/staging-smoke-checklist.md.
- The Silo admin API key is never stored by the module. It lives only
where WHMCS keeps it (encrypted in the server Password / Access Hash field),
is read transiently per request, and is sent only as the
Authorizationheader to Silo. - User passwords are not stored at rest by the module. On CreateAccount /
ChangePassword the password is forwarded from WHMCS to Silo in memory
only. Both the admin
Reset Passwordbutton and the customer's client-area "reset password & sign out" action write the new password back to the WHMCS service password field, which WHMCS stores encrypted — that is the only persistence, and it is WHMCS's standard encrypted store, not a module-specific one. The client-area action additionally shows the new password once in the returned confirmation message (so the customer can capture it); it is not persisted anywhere else by the module. - In the Module Log the API key and payload passwords are masked, and
customer IP addresses (
client_ip/ip_address) in Silo responses — e.g. from the sessions endpoint — are redacted before the body is logged. - Passwords are unavoidably cleartext in memory during a request and in transit over HTTPS to Silo.
- HTTPS is required for public Silo hosts. If a server is configured
without the Secure (SSL/TLS) option and its hostname is not a
loopback/private-range address (localhost,
127.x,10.x,172.16–31.x,192.168.x,169.254.x, IPv6::1/fc00::/7), the module refuses to run — it will not send the admin API key or passwords in cleartext to a public host. The Servers-page Test Connection button surfaces this as a config error telling the operator to enable Secure. Plaintext HTTP is tolerated only for local/LAN backends where there is no untrusted network in between. - TLS peer and hostname verification are enabled explicitly on both the cURL and stream transports.
- The client-area "reset password & sign out" action is rate-limited per
service (cooldown stored in
mod_silo_pw_reset) so the button cannot be spammed to repeatedly sign a customer out of every device. The cooldown is set per product via the Client reset cooldown (seconds) config option (default 60; set 0 to disable).
- Store the Silo admin API key only in the WHMCS server Password / Access Hash field. Use HTTPS for Silo.
- Keep
bad_words.txt, local environment files, and release archives out of git. The username profanity filter matches listed words as substrings (case-folded), sofuckin the list also rejectsfuckers. - Treat WHMCS module/activity logs as sensitive operational context.
Proprietary. All rights reserved.