Back to Documentation
Code theme:

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

POST/api/licenses/validate

Validate 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"
POST/api/licenses/activate

Activate 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"
POST/api/licenses/deactivate

Deactivate 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