This guide covers the full loop: activate → store → verify on startup → deactivate. It assumes you already have a workspace and a license key.
Requires PHP 8+ with ext-sodium and ext-curl. Verification uses sodium’s native Ed25519 API.
1. Activate
license.php
<?php
const BASE = 'https://keygate.unionuser.com';
function activate(string $licenseKey, string $deviceId): string {
$ch = curl_init(BASE . '/api/v1/license/activate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'license_key' => $licenseKey,
'identifier' => $deviceId,
'identifier_type' => 'device',
]),
CURLOPT_TIMEOUT => 15,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$body = json_decode($raw, true);
if ($status !== 200 || empty($body['success'])) {
throw new RuntimeException($body['error']['code'] ?? 'ACTIVATE_FAILED');
}
return $body['data']['token'];
}
2. Verify offline
verify.php
<?php
const RAW_PUBLIC = 'bf17435ee7b937b3592808941683c21d37c4e124d95d4e8fdd86fe775dd23b11';
function verifyToken(string $token, string $deviceId): array {
[$payloadB64, $sigB64] = explode('.', $token, 2);
$signature = base64UrlDecode($sigB64);
$publicKey = hex2bin(RAW_PUBLIC);
if (!sodium_crypto_sign_verify_detached($signature, $payloadB64, $publicKey)) {
throw new RuntimeException('INVALID_SIGNATURE');
}
$claims = json_decode(base64UrlDecode($payloadB64), true);
if (($claims['did'] ?? null) !== $deviceId) {
throw new RuntimeException('DEVICE_MISMATCH');
}
if (($claims['exp'] ?? 0) < time()) {
throw new RuntimeException('TOKEN_EXPIRED');
}
return $claims;
}
function base64UrlDecode(string $value): string {
$value = strtr($value, '-_', '+/');
$pad = strlen($value) % 4;
if ($pad > 0) $value .= str_repeat('=', 4 - $pad);
return base64_decode($value, true);
}
3. Deactivate
deactivate.php
<?php
$ch = curl_init(BASE . '/api/v1/license/deactivate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'license_key' => $licenseKey,
'identifier' => $deviceId,
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
Deployment notes
- Make sure sodium is enabled; some distributions need it installed separately.
- Never store keys or tokens in downloadable files under the web root.
- For desktop apps, prefer a local runtime or a thin local service.