View Categories

Licensing and Activation

RackHelm is licensed software and checks its licence against a remote server. The check is designed to be invisible when everything is healthy — one call a day, cached in between, with a grace window that keeps your portal running through a network outage. This article explains what is checked, what is transmitted, and what happens when the check fails.

Where the key lives #

Your licence key sits in .env:

RACKHELM_LICENSE_KEY="your-licence-key"

The installer writes it for you from the License Key prompt. Installs that predate the RackHelm rename may instead have ORBI_LICENSE_KEY, which is still read as a fallback. Set one or the other, not both.

Warning: The key is read from .env at runtime, which is why RackHelm never uses php artisan config:cache. Running it would freeze whatever key was present at the moment of caching and ignore anything set afterwards.

How the check runs #

RackHelm enforces the licence at two independent points in every web request: once when the application boots and once in the EnsureLicensed middleware, which is prepended to the global middleware stack. The check itself is memoised per request, so having both costs nothing when the licence is valid. Console commands — Artisan, the queue worker, the scheduler — do not run the boot-time check.

A single check works like this:

  1. Build a hardware fingerprint for this server: an MD5 of the server’s IP address and its hostname.
  2. Read the cached verdict from storage/app/private/.orbi_license_cache. The cache is only trusted if its cryptographic signature verifies against a public key baked into the build, it is bound to this licence key and this fingerprint, its status is valid, and it is not past its signed expiry.
  3. If that cached verdict is less than 24 hours old, use it and make no network call at all.
  4. Otherwise send a heartbeat to https://license.orbi-hosting.com/api/v1/licenses/heartbeat over HTTPS with certificate verification enforced. The connection phase is limited to 4 seconds and the whole call to 7 seconds.
  5. Verify the reply’s signature, check it echoes the one-time nonce this server generated, and check it is bound to the same licence key and fingerprint. If the status is valid, cache it and continue.

Concurrent requests that all miss the cache are serialised behind a lock, so a busy portal makes one heartbeat rather than hundreds.

Offline behaviour #

If the licence server cannot be reached — a network failure or an HTTP 5xx — RackHelm falls back to the cached verdict for up to 3 days from the moment the licence server issued it, and never past the licence’s own expiry date. During a known outage, further network attempts are suppressed for 5 minutes at a time so that a dead endpoint does not add a 7-second stall to every page load.

Two edge cases are handled strictly rather than generously. A cached verdict whose issue time is more than an hour in the server’s future is treated as a rolled-back clock and refused. And a perpetual licence with no signed issue time is never served from offline grace, because nothing would ever bound it — it forces a fresh online check instead.

What is transmitted #

Every heartbeat carries four protocol fields — the licence key, the hardware fingerprint, a random nonce and the request’s hostname — plus the platform version and a statistics snapshot.

The snapshot is counts and version strings only. It is support and sizing data:

Group Contents
Platform Product name, version number, release channel, release date.
Environment PHP version and SAPI, Laravel version, OS family, kernel release, architecture, web-server banner, database driver and server version, the configured queue, cache, session, mail and filesystem drivers, and whether the app is in production, has debug on, and is on HTTPS.
Runtime ionCube Loader version, whether OPcache is on, memory_limit, max_execution_time, and which of ten expected PHP extensions are loaded.
Install Approximate first-boot date, when the build was last deployed, and the migration count, latest migration name and batch number.
Usage Counts of clients, staff, sub-accounts and guest records; products, product groups, addons and coupons; orders, services, active services, invoices, unpaid invoices and SSL certificates; tickets, open tickets and departments; content pages and menus; the number of staff roles; the names of enabled integration drivers and payment gateways with a flag for test mode.

What is deliberately not sent: customer names, e-mail addresses, the domains you manage, order contents, ticket text, API credentials and any financial figure. The rule the code holds itself to is that every field is a count or a version string.

Telemetry collection is wrapped so it can never take a licensed platform offline over a metric — if the database is down or mid-migration, each probe degrades to “unknown” and the heartbeat still goes out with the version.

The version number #

The reported version comes from a constant compiled into the build. There is no .env variable or database setting that overrides it, deliberately: the number is only useful if it describes the code that is actually running. You can see it in the admin sidebar footer.

The admin sidebar footer showing the RackHelm product name and version number
The version in the sidebar footer is the same number reported to the licence server.

When the check fails #

A failed check ends the request with a plain page reading Licence validation error: followed by a short message. The detail is written to storage/logs/laravel.log prefixed [License] with a code:

Code Meaning What to do
missing_key No licence key is configured. Set RACKHELM_LICENSE_KEY in .env.
unreachable The licence server could not be reached and there is no cached verdict within grace. Check outbound HTTPS to license.orbi-hosting.com is not blocked by a firewall or egress proxy.
bad_response The server replied, but not with a signed payload — usually a rejection with an explanatory message. Read the logged message; it is passed through from the licence server.
bad_signature The reply’s signature did not verify. Something is intercepting the connection. Check for a TLS-inspecting proxy.
binding_mismatch The reply was not bound to this key and this server. Usually a proxied or replayed response. Contact support.
rejected The licence is invalid, suspended or expired. Contact support with the reason shown.
Browser showing the plain Licence validation error page
A licence failure replaces the whole page; the diagnostic detail is in laravel.log, not on screen.

Moving a server #

The fingerprint is derived from the server’s IP address and hostname, so migrating to a new machine, changing the primary IP, or renaming the host produces a new fingerprint. The cached verdict from the old machine will not be accepted — it is bound to the old fingerprint — and the next heartbeat will present the new one. If your licence is bound to a single host, arrange the rebinding with support before you cut over.

Note: Copying storage/app/private/.orbi_license_cache to another server does nothing. The cache is signature-verified and fingerprint-bound on every read, so it is rejected on any machine but the one it was issued for.

Troubleshooting #

Symptom Cause and fix
Portal works, then stops exactly three days later Offline grace expired. The licence server has been unreachable since the last successful heartbeat — check egress firewalling and DNS.
Every page is slow by roughly 7 seconds Heartbeats are timing out. RackHelm suppresses retries for 5 minutes after a failure, so persistent slowness means something is answering slowly rather than failing outright.
The licence works on the website but Artisan commands behave oddly Unrelated — the boot-time check is skipped in console. Look elsewhere.
Licence failed immediately after restoring a backup onto a new box New IP or hostname means a new fingerprint. Contact support to rebind.
Key set in .env but still missing_key A duplicate RACKHELM_LICENSE_KEY= line earlier in the file is winning — the first occurrence takes precedence. Delete the empty one.