License API
Integrate license activation, validation, and update checks into your software products.
{{ $baseUrl }}/api
● HMAC-SHA256 Auth
JSON · POST only
Overview
All endpoints accept POST requests with a JSON body and respond with JSON.
Every request is authenticated using a per-license HMAC-SHA256 signature —
no API keys or Bearer tokens. The license secret key is shown in each license's credentials page.
Authentication
Every request body must include three security fields in addition to the endpoint-specific fields. The secret key is unique per license and available in the customer portal under Licenses → [license] → Credentials.
| Field | Type | Description |
|---|---|---|
timestamp |
integer | Unix timestamp (seconds). Must be within ±300 seconds of the server clock. |
nonce |
string | Random unique hex string (16+ chars). Each nonce can only be used once within a 10-minute window — prevents replay attacks. |
signature |
string | HMAC-SHA256 hex digest of the payload computed with the license's secret key. See Signing Requests. |
Signing Requests
The signature is computed over the entire payload (excluding the signature field itself)
using the license secret key. Steps:
- Add
timestamp=time()andnonce= random 16-byte hex to the payload. - Sort all payload keys alphabetically (
ksort). - Build the message:
timestamp + ":" + nonce + ":" + json_encode(sorted_payload) - Compute:
signature = hash_hmac('sha256', message, secretKey) - Add
signatureto the payload and POST the full JSON.
function signRequest(array $payload, string $secretKey): array
{
$payload['timestamp'] = time();
$payload['nonce'] = bin2hex(random_bytes(16));
$data = $payload;
ksort($data); // sort alphabetically — required
$message = $payload['timestamp'] . ':' . $payload['nonce'] . ':' . json_encode($data);
$payload['signature'] = hash_hmac('sha256', $message, $secretKey);
return $payload; // now contains timestamp, nonce, signature
}
function apiPost(string $url, array $payload): ?object
{
$context = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
'content' => json_encode($payload),
'timeout' => 10,
'ignore_errors' => true,
]]);
$body = @file_get_contents($url, false, $context);
return $body ? json_decode($body) : null;
}
PHP SDK — Complete Drop-in Class
Copy this class into your project. It handles signing, requests, token storage, and all 5 endpoints.
<?php
class LicensePlatformSDK
{
public function __construct(
private string $licenseKey,
private string $secretKey,
private string $baseUrl = '{{ $baseUrl }}'
) {}
// Call once on first install
public function activate(array $extra = []): ?object
{
$response = $this->req('activate', array_merge([
'domain' => $_SERVER['HTTP_HOST'] ?? gethostname(),
'installation_id' => hash('sha256', gethostname()),
], $extra));
if ($response?->success) {
$this->saveToken([
'activation_token' => $response->activation_token,
'activation_id' => $response->activation_id,
'expires_at' => $response->expires_at,
]);
}
return $response;
}
// Call on every app boot — returns true if valid
public function validate(): bool
{
$token = $this->loadToken();
if (!$token) return false;
$r = $this->req('validate', ['activation_token' => $token['activation_token']]);
return (bool) ($r?->valid ?? false);
}
// Daily heartbeat — returns response with expires_at
public function heartbeat(): ?object
{
$token = $this->loadToken();
if (!$token) return null;
return $this->req('heartbeat', ['activation_token' => $token['activation_token']]);
}
// Call from uninstall to free up an activation slot
public function deactivate(): bool
{
$token = $this->loadToken();
if (!$token) return false;
$r = $this->req('deactivate', ['activation_token' => $token['activation_token']]);
if ($r?->success) { @unlink($this->tokenPath()); return true; }
return false;
}
// Check for new version — pass the currently installed version
public function checkUpdate(string $currentVersion): ?object
{
return $this->req('check-update', ['current_version' => $currentVersion]);
}
// ── Private helpers ──────────────────────────────────────────
private function req(string $endpoint, array $data): ?object
{
$data['license_key'] = $this->licenseKey;
$payload = $this->sign($data);
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
'content' => json_encode($payload),
'timeout' => 10,
'ignore_errors' => true,
]]);
$body = @file_get_contents(rtrim($this->baseUrl,'/').'/api/license/'.$endpoint, false, $ctx);
return $body ? json_decode($body) : null;
}
private function sign(array $p): array
{
$p['timestamp'] = time();
$p['nonce'] = bin2hex(random_bytes(16));
$d = $p; ksort($d);
$p['signature'] = hash_hmac('sha256', $p['timestamp'].':'.$p['nonce'].':'.json_encode($d), $this->secretKey);
return $p;
}
private function tokenPath(): string
{
return sys_get_temp_dir() . '/lp_' . md5($this->licenseKey) . '.json';
}
private function saveToken(array $data): void
{
file_put_contents($this->tokenPath(), json_encode($data));
}
private function loadToken(): ?array
{
$p = $this->tokenPath();
return file_exists($p) ? json_decode(file_get_contents($p), true) : null;
}
}
// ── Usage ─────────────────────────────────────────────────────────────────────
$sdk = new LicensePlatformSDK(
licenseKey: 'ABCD1234-EFGH5678-IJKL9012-MNOP3456',
secretKey: 'secret-key-from-license-credentials-page'
);
// First install
$r = $sdk->activate();
if (!$r?->success) die('Activation failed: ' . ($r?->message ?? 'no response'));
// Every boot
if (!$sdk->validate()) die('License invalid.');
// Hourly / daily cron
$sdk->heartbeat();
// Uninstall
$sdk->deactivate();
activation_token; it is required for all subsequent calls.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
{{ $f }} |
{{ $t }} | {{ $r==='yes'?'required':($r==='rec'?'recommended':'optional') }} | {{ $d }} |
Success (200)
{
"success": true,
"message": "activated",
"license_key": "ABCD1234-...",
"activation_id": "uuid-v4",
"activation_token": "64-char-hex",
"status": "active",
"type": "regular",
"product": "Your Product",
"expires_at": "2026-12-31T00:00:00Z",
"is_lifetime": false,
"max_activations": 3,
"activations_used": 1,
"timestamp": 1748000001,
"signature": "server-hmac"
}Error (422)
{
"success": false,
"error": "MAX_ACTIVATIONS",
"message": "Maximum activations reached."
}
// Possible error codes:
// INVALID_LICENSE
// INVALID_SIGNATURE
// MAX_ACTIVATIONS
// INVALID_DOMAINlast_validated_at on the server.
Request
{
"license_key": "ABCD1234-...",
"activation_token": "64-char-hex",
// OR use domain instead:
"domain": "example.com",
"timestamp": 1748000000,
"nonce": "abc123...",
"signature": "hmac-sha256"
}Success (200)
{
"valid": true,
"license_key": "ABCD1234-...",
"status": "active",
"type": "regular",
"product": "Your Product",
"expires_at": "2026-12-31T00:00:00Z",
"is_lifetime": false,
"activation_id": "uuid-v4",
"timestamp": 1748000001,
"signature": "server-hmac"
}
// Errors: INVALID_LICENSE,
// INVALID_SIGNATURE, NOT_ACTIVATED,
// TAMPER_DETECTED{
"license_key": "ABCD1234-...",
"activation_token": "64-char-hex",
"timestamp": 1748000000,
"nonce": "abc123...",
"signature": "hmac-sha256"
}{
"alive": true,
"expires_at": "2026-12-31T00:00:00Z",
"server_time": "2026-06-02T10:00:00Z",
"timestamp": 1748000001,
"signature": "server-hmac"
}
// Errors: INVALID_REQUEST, NOT_FOUND{
"license_key": "ABCD1234-...",
"activation_token": "64-char-hex",
// OR: "domain": "example.com"
"timestamp": 1748000000,
"nonce": "abc123...",
"signature": "hmac-sha256"
}{
"success": true,
"message": "Activation removed.",
"timestamp": 1748000001,
"signature": "server-hmac"
}
// Errors: INVALID_REQUEST, NOT_FOUND{
"license_key": "ABCD1234-...",
"current_version": "2.0.1",
"timestamp": 1748000000,
"nonce": "abc123...",
"signature": "hmac-sha256"
}{
"update_available": true,
"current_version": "2.0.1",
"latest_version": "2.1.0",
"changelog": "- Fixed X\n- Added Y",
"download_url": "https://panel.../dl/...",
"checksum": "sha256-hex",
"timestamp": 1748000001,
"signature": "server-hmac"
}
// No update: update_available: false
// changelog + download_url = nullError Codes
All errors return HTTP 422 with "success": false. Rate limit violations return HTTP 429.
| Code | HTTP | Meaning & Action |
|---|---|---|
| {{ $code }} | {{ $http }} | {{ $desc }} |
Rate Limiting
| Limit | Value | Notes |
|---|---|---|
| {{ $l }} | {{ $v }} | {{ $n }} |