License Keys
Complete guide to integrating license key validation and device management
Overview
The license key system provides a robust way to manage product access through unique keys. Each license key supports device-based activation limits, subscription status validation, and flexible management capabilities.
Key Features
- •Device-based activation with configurable limits (1-10 devices per key)
- •Automatic subscription status validation (active, cancelled, past due)
- •Idempotent activation (same device can activate multiple times)
- •License revocation and device deactivation support
- •Device activity tracking with last seen timestamps
API Endpoints
/api/licenses/validateValidate a license key and check if it's active
Request Body
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"deviceFingerprint": "unique-device-id" // Optional
}Success Response (200)
{
"valid": true,
"status": "active",
"email": "buyer@example.com",
"productName": "Pro License",
"productId": "abc123",
"purchaseDate": "2024-01-15T00:00:00Z",
"expiresAt": null,
"subscription": {
"active": true,
"cancelAtPeriodEnd": false,
"interval": "monthly"
},
"device": {
"activationsUsed": 2,
"activationsAllowed": 3,
"remainingActivations": 1,
"isDeviceActivated": true // Only if deviceFingerprint provided
}
}Error Response (400/404)
{
"valid": false,
"error": "License key not found",
"status": "revoked"
}
// Possible error messages:
// - "License key not found"
// - "License key has been revoked"
// - "Subscription has been cancelled"
// - "Payment is past due"/api/licenses/activateActivate a device for a license key
Request Body
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"deviceFingerprint": "unique-device-id",
"deviceName": "My MacBook Pro" // Optional
}Success Response (200)
{
"success": true,
"message": "Device activated successfully",
"activation": {
"deviceFingerprint": "unique-device-id",
"deviceName": "My MacBook Pro",
"activatedAt": "2024-01-15T00:00:00Z"
},
"activationsUsed": 1,
"activationsAllowed": 3
}Error Response (400)
{
"success": false,
"error": "Maximum device limit reached",
"activationsUsed": 3,
"activationsAllowed": 3
}
// Other possible errors:
// - "License key not found"
// - "License key is not active"
// - "Missing required fields"/api/licenses/deactivateDeactivate a device from a license key
Request Body
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"deviceFingerprint": "unique-device-id"
}Success Response (200)
{
"success": true,
"message": "Device deactivated successfully"
}Error Response (400/404)
{
"success": false,
"error": "Device activation not found"
}
// Other possible errors:
// - "License key not found"
// - "Missing required fields"Integration Guide
Basic License Validation
Validate a license key on application startup or login:
async function validateLicense(licenseKey) {
const response = await fetch('https://yourplatform.com/api/licenses/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licenseKey })
});
const result = await response.json();
if (!result.valid) {
throw new Error(result.error || 'Invalid license');
}
return result;
}
// Usage
try {
const license = await validateLicense('XXXX-XXXX-XXXX-XXXX');
console.log('License valid:', license.status);
console.log('Product:', license.productName);
console.log('Device slots:', license.device.activationsUsed, '/', license.device.activationsAllowed);
} catch (error) {
console.error('License validation failed:', error.message);
}Device Activation
Activate a device with a license key:
// Generate a unique device fingerprint
// You can use libraries like fingerprintjs2 or create your own
function getDeviceFingerprint() {
// Example using machine ID, OS, and hardware info
const components = [
navigator.userAgent,
navigator.platform,
// Add more hardware-specific identifiers
];
return btoa(components.join('|'));
}
async function activateDevice(licenseKey) {
const deviceFingerprint = getDeviceFingerprint();
const deviceName = `${navigator.platform} - ${new Date().toISOString()}`;
const response = await fetch('https://yourplatform.com/api/licenses/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey,
deviceFingerprint,
deviceName
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Activation failed');
}
return result;
}
// Usage
try {
const activation = await activateDevice('XXXX-XXXX-XXXX-XXXX');
console.log('Device activated:', activation.activation.deviceName);
console.log('Activations used:', activation.activationsUsed, '/', activation.activationsAllowed);
} catch (error) {
if (error.message.includes('Maximum device limit')) {
console.error('All device slots are used. Please deactivate a device first.');
} else {
console.error('Activation failed:', error.message);
}
}Complete Integration Example
Full example with validation and automatic activation:
class LicenseManager {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.licenseKey = localStorage.getItem('licenseKey');
this.deviceFingerprint = this.getDeviceFingerprint();
}
getDeviceFingerprint() {
// Store or generate device fingerprint
let fingerprint = localStorage.getItem('deviceFingerprint');
if (!fingerprint) {
fingerprint = this.generateFingerprint();
localStorage.setItem('deviceFingerprint', fingerprint);
}
return fingerprint;
}
generateFingerprint() {
// Implement your fingerprint logic
return btoa(navigator.userAgent + navigator.platform);
}
async validateAndActivate(licenseKey) {
// First, validate the license
const validation = await this.validate(licenseKey);
if (!validation.valid) {
throw new Error(validation.error);
}
// Check if this device is already activated
if (!validation.device.isDeviceActivated) {
// Activate this device
await this.activate(licenseKey);
}
// Store license key
this.licenseKey = licenseKey;
localStorage.setItem('licenseKey', licenseKey);
return validation;
}
async validate(licenseKey) {
const response = await fetch(`${this.baseUrl}/api/licenses/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey: licenseKey || this.licenseKey,
deviceFingerprint: this.deviceFingerprint
})
});
return await response.json();
}
async activate(licenseKey) {
const response = await fetch(`${this.baseUrl}/api/licenses/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey,
deviceFingerprint: this.deviceFingerprint,
deviceName: navigator.platform
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error);
}
return result;
}
async deactivate() {
if (!this.licenseKey) {
throw new Error('No license key found');
}
const response = await fetch(`${this.baseUrl}/api/licenses/deactivate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey: this.licenseKey,
deviceFingerprint: this.deviceFingerprint
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error);
}
// Clear stored license
localStorage.removeItem('licenseKey');
this.licenseKey = null;
return result;
}
}
// Usage
const licenseManager = new LicenseManager('https://yourplatform.com');
// On app startup
async function initializeApp() {
try {
if (licenseManager.licenseKey) {
// Validate existing license
const validation = await licenseManager.validate();
console.log('License valid:', validation.productName);
} else {
// Prompt user for license key
const userLicenseKey = prompt('Enter your license key:');
await licenseManager.validateAndActivate(userLicenseKey);
console.log('License activated successfully!');
}
} catch (error) {
console.error('License error:', error.message);
// Handle error (show UI, etc.)
}
}
initializeApp();Best Practices
Device Fingerprinting
Use a consistent, unique identifier for each device. Consider using hardware-based identifiers (MAC address, CPU ID, etc.) or libraries like FingerprintJS. Avoid using easily-changeable values like IP addresses.
Periodic Validation
Validate licenses periodically (e.g., daily) to check for subscription cancellations or revocations. Don't validate on every app launch to avoid unnecessary API calls and poor offline experience.
Error Handling
Handle network errors gracefully. If validation fails due to network issues, allow the app to continue with cached validation results. Only block access for explicit invalid license responses.
User Experience
When device limits are reached, provide clear instructions on how to deactivate devices through your dashboard. Consider showing which devices are currently activated with their last seen timestamps.
Rate Limits
API endpoints are rate limited to prevent abuse:
/validate: 100 requests per minute per license key/activate: 10 requests per minute per license key/deactivate: 10 requests per minute per license key
If you exceed these limits, you'll receive a 429 (Too Many Requests) response. Implement exponential backoff in your retry logic.
License Key Format
License keys follow the format: XXXX-XXXX-XXXX-XXXX
- 16 characters divided into 4 segments of 4 characters
- Character set: A-Z (excluding I, O) and 2-9 (excluding 0, 1)
- Case-insensitive (automatically normalized to uppercase)
- Cryptographically secure random generation