Software licensing implementation via website
Selling software licenses via a website includes: license key generation after payment, activation and binding to device, license verification in application, subscription management, and version updates.
Licensing models
| Model | Description | Technically |
|---|---|---|
| Perpetual | One-time version purchase | Key without expiry, no online verification |
| Subscription | Monthly/yearly payment | JWT token with expiry, online validation |
| Per-seat | Payment per user count | Activation counter per key |
| Node-locked | Binding to specific device | HWID fingerprint |
License key generation
class LicenseKeyGenerator
{
private const CHARSET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
public function generate(int $segments = 5, int $segmentLength = 5): string
{
$key = '';
for ($s = 0; $s < $segments; $s++) {
for ($c = 0; $c < $segmentLength; $c++) {
$key .= self::CHARSET[random_int(0, strlen(self::CHARSET) - 1)];
}
if ($s < $segments - 1) $key .= '-';
}
return $key; // Example: A3K7M-XQ2WP-N8VLR-5HZTB-J4YCS
}
public function generateSigned(array $metadata): string
{
$base = $this->generate();
$hash = substr(hash_hmac('sha256', $base . json_encode($metadata), config('app.key')), 0, 8);
return $base . '-' . strtoupper($hash);
}
}
License activation API
class LicenseController extends Controller
{
public function activate(Request $request): JsonResponse
{
$key = $request->input('license_key');
$hwid = $request->input('hardware_id');
$appVersion = $request->input('app_version');
$license = License::where('key', $key)->firstOrFail();
if ($license->status !== 'active') {
return response()->json(['error' => 'License is not active'], 403);
}
if ($license->expires_at && now()->isAfter($license->expires_at)) {
return response()->json(['error' => 'License expired'], 403);
}
// Check activation count
$activationCount = $license->activations()->where('revoked', false)->count();
if ($activationCount >= $license->max_activations) {
return response()->json(['error' => "Maximum activations ({$license->max_activations}) reached"], 403);
}
// Create activation
$activation = $license->activations()->create([
'hardware_id' => $hwid,
'ip_address' => $request->ip(),
'app_version' => $appVersion,
'activated_at'=> now(),
]);
// Return JWT license token
$token = JWT::encode([
'iss' => 'example.com',
'sub' => $license->id,
'exp' => $license->expires_at?->timestamp ?? PHP_INT_MAX,
'hwid' => $hwid,
'product' => $license->product_code,
'plan' => $license->plan,
], config('app.license_secret'), 'HS256');
return response()->json(['token' => $token, 'expires_at' => $license->expires_at]);
}
}
Online license validation in application
# In Python application on each startup
import jwt
import requests
from datetime import datetime
class LicenseValidator:
def __init__(self, license_key: str, hw_id: str):
self.license_key = license_key
self.hw_id = hw_id
self.token_cache_file = '.license_cache'
def validate(self) -> bool:
# First check cached token
cached = self._load_cached_token()
if cached and self._verify_token(cached):
return True
# Online activation
try:
resp = requests.post('https://api.example.com/v1/licenses/activate', {
'license_key': self.license_key,
'hardware_id': self.hw_id,
}, timeout=5)
if resp.status_code == 200:
token = resp.json()['token']
self._cache_token(token)
return self._verify_token(token)
except requests.exceptions.RequestException:
# Offline mode: use cache
return cached is not None
return False
def _verify_token(self, token: str) -> bool:
try:
payload = jwt.decode(token, LICENSE_PUBLIC_KEY, algorithms=['RS256'])
return payload.get('hwid') == self.hw_id
except jwt.ExpiredSignatureError:
return False
except jwt.InvalidTokenError:
return False
Payment system integration
// Webhook on successful payment
class HandleSuccessfulPayment
{
public function handle(PaymentSucceeded $event): void
{
$order = $event->order;
$product = $order->product;
for ($i = 0; $i < $order->quantity; $i++) {
License::create([
'order_id' => $order->id,
'product_code' => $product->code,
'key' => app(LicenseKeyGenerator::class)->generateSigned(['product' => $product->code]),
'plan' => $order->plan,
'max_activations' => $product->max_activations,
'expires_at' => $product->subscription_period
? now()->add($product->subscription_period)
: null,
'status' => 'active',
]);
}
Mail::to($order->customer_email)->send(new LicenseKeysMail($order));
}
}
Timeline
Software licensing system with key generation, activation, and API validation: 12–16 working days.







