LicenseHub
DOCUMENTS

Install your license

Connect a PHP website to LicenseHub, bind the license to its current hostname, and move it later with self-service reissue.

How licensing works

Your PHP script sends the license key, product slug, a stable installation ID and the website hostname to the validation API. The first valid request creates the website binding. Later requests must match that binding.

Moving websites?Do not change the key. Sign in to LicenseHub, open your license and choose Reissue license. The old binding is removed and the next website to validate the key becomes the new licensed installation.

License plans and expiry

Your license may be issued on a Free, Monthly, Yearly, Lifetime or custom administrator-defined plan. The selected plan determines the license expiry date and maximum activation count at the moment the license is issued. A Lifetime plan has no automatic expiry.

Plan names, durations, activation limits and product availability are controlled by the LicenseHub administrator.

PHP installation

  1. Copy your license key from the client dashboard.
  2. Find your product slug in the install guide link for that license.
  3. Create a server-side file such as license-check.php.
  4. Paste the example below and replace YOUR-LICENSE-KEY and YOUR_API_SIGNING_SECRET.
  5. Require the checker early in the protected script, before protected functionality is executed.
<?php
// license-check.php
// Store these values in a config file outside your public web root when possible.
$licenseKey = 'YOUR-LICENSE-KEY';
$productSlug = 'your-product-slug';
$licensingUrl = 'https://license.fibrenode.co.uk/api/validate.php';
$apiSigningSecret = 'YOUR_API_SIGNING_SECRET';

// Use the website hostname as the installation identity.
$host = strtolower($_SERVER['HTTP_HOST'] ?? '');
$host = preg_replace('/:\d+$/', '', $host);
if ($host === '') {
    exit('Unable to determine licensed hostname.');
}
$instanceId = hash('sha256', $productSlug . '|' . $host);

$request = json_encode([
    'license_key' => $licenseKey,
    'product' => $productSlug,
    'instance_id' => $instanceId,
    'domain' => $host,
], JSON_UNESCAPED_SLASHES);

$ch = curl_init($licensingUrl);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => $request,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($response === false || $error !== '') {
    exit('Unable to contact the licensing server.');
}
$data = json_decode($response, true);
if (!is_array($data)) {
    exit('Invalid licensing response.');
}

// Verify the response signature before trusting the result.
$signature = (string)($data['signature'] ?? '');
unset($data['signature']);
$canonical = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$expected = hash_hmac('sha256', $canonical, $apiSigningSecret);
if ($signature === '' || !hash_equals($expected, $signature)) {
    exit('Licensing response signature failed.');
}

if ($httpCode !== 200 || empty($data['valid'])) {
    $reason = (string)($data['reason'] ?? 'unknown');
    exit('License validation failed: ' . htmlspecialchars($reason, ENT_QUOTES, 'UTF-8'));
}

Then load it from the protected application:

<?php
require __DIR__ . '/license-check.php';
// Your licensed application continues here.

Validation API

Endpoint: https://license.fibrenode.co.uk/api/validate.php

{
  "license_key": "LIC-...",
  "product": "your-product-slug",
  "instance_id": "sha256-product-and-host",
  "domain": "customer.example"
}

A valid response returns HTTP 200 with valid: true. Revoked, suspended, expired, disabled-product, activation-limit and domain-mismatch responses return valid: false with a reason.

Reissuing to another website URL

After a license has been used on a website, the dashboard shows its current domain and a Reissue license button. Reissuing deletes the current activation binding but keeps the same license key.

1Move/upload your script to the new website.
2In LicenseHub, click Reissue license on the old binding.
3Open the script on the new domain. Its next validation claims the license.

The administrator can disable reissues or configure a cooldown between reissues. If the button is unavailable, contact the licensing administrator.

Offline validation and signed certificates

Valid API responses now include an Ed25519-signed certificate when the PHP Sodium extension is available. The certificate contains the product, domain, instance, expiry, refresh time and grace-period deadline. Your application can cache the last successful certificate and continue during a temporary licensing-server outage until grace_until.

Do not extend the grace period locally. Refresh at the product's configured validation interval and reject cached certificates after their signed grace deadline.

Security notes

Keep license validation server-side and always use HTTPS for LicenseHub. The example verifies the online HMAC signature with hash_equals(). For offline operation, verify the Ed25519 certificate using sodium_crypto_sign_verify_detached() and the public key returned by LicenseHub. A licensing check controls authorization, but it does not encrypt or encode PHP source code; source protection is a separate layer.

Software updates & protected downloads

Use /api/update.php with license_key, product, current_version and optional channel. The response reports whether a newer published release exists and includes the release SHA-256 hash. Customer package downloads are served through /download.php only after LicenseHub confirms an active, unexpired license for the product.

Webhook verification

Each webhook request includes X-LicenseHub-Signature: sha256=.... Compute HMAC-SHA256 over the raw request body with that webhook's secret and compare it using hash_equals().