🚀 Activation Flow¶
🌐 Online Activation¶
User Flow Diagram¶
┌─────────────────────────────────────────────────────────────┐
│ ONLINE ACTIVATION │
└─────────────────────────────────────────────────────────────┘
User launches plugin
↓
[No license found]
↓
┌──────────────────────┐
│ Enter License Key │
│ XXXX-XXXX-XXXX-XXXX │
│ │
│ [Activate Online] │
└──────────────────────┘
↓
Generate machine fingerprint
↓
Send to license server:
- License key
- Machine ID
- Product version
- OS info
↓
┌─────────────────────────────────┐
│ SERVER VALIDATION │
├─────────────────────────────────┤
│ ✓ Key format correct? │
│ ✓ Key exists in database? │
│ ✓ Not expired? │
│ ✓ Seat limit not exceeded? │
│ ✓ Not revoked/blacklisted? │
└─────────────────────────────────┘
↓
┌─────────────────┐
│ IF SUCCESS: │
│ - Generate │
│ license file │
│ - Return to │
│ client │
│ - Log activation│
└─────────────────┘
↓
Plugin saves license.json locally
↓
✅ ACTIVATION COMPLETE
↓
Plugin loads fully featured
API Endpoint¶
POST /api/v1/activate
// Request
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"machine_id": "a1b2c3d4e5f6",
"product": "AudioLab Pro",
"product_version": "1.0.1",
"os": "Windows 10",
"plugin_format": "VST3"
}
// Response (Success)
{
"success": true,
"license": {
"version": 1,
"license_key": "XXXX-XXXX-XXXX-XXXX",
"email": "user@example.com",
"name": "John Doe",
"product": "AudioLab Pro",
"license_type": "commercial",
"issued_at": "2025-01-15T10:30:00Z",
"expires_at": "2026-01-15T10:30:00Z",
"seat_id": 1,
"features": {
"max_instances": 16,
"oversampling": true
},
"signature": "BASE64_RSA_SIG..."
}
}
// Response (Error)
{
"success": false,
"error_code": "SEAT_LIMIT_EXCEEDED",
"message": "This license is already activated on 2 machines. Please deactivate one or contact support.",
"support_url": "https://audiolab.com/support/license"
}
Error Codes¶
| Code | Message | User Action |
|---|---|---|
INVALID_KEY |
License key not found | Check key, contact support |
EXPIRED |
License has expired | Renew subscription |
SEAT_LIMIT_EXCEEDED |
Too many activations | Deactivate old machine |
REVOKED |
License revoked | Contact support |
VERSION_MISMATCH |
Product version not covered | Upgrade license |
NETWORK_ERROR |
Cannot reach server | Try offline activation |
Client Implementation¶
class OnlineActivation {
public:
struct Result {
bool success;
std::string error_code;
std::string message;
nlohmann::json license_data;
};
Result activate(const std::string& license_key) {
// Generate machine fingerprint
std::string machine_id = MachineFingerprint::generate();
// Prepare request
nlohmann::json request = {
{"license_key", license_key},
{"machine_id", machine_id},
{"product", "AudioLab Pro"},
{"product_version", PRODUCT_VERSION},
{"os", getOSName()},
{"plugin_format", PLUGIN_FORMAT}
};
// Send HTTP POST request
auto response = httpPost(
"https://license.audiolab.com/api/v1/activate",
request.dump(),
{"Content-Type: application/json"}
);
if (response.status_code != 200) {
return {false, "NETWORK_ERROR", "Cannot reach license server", {}};
}
auto response_json = nlohmann::json::parse(response.body);
if (!response_json["success"].get<bool>()) {
return {
false,
response_json["error_code"],
response_json["message"],
{}
};
}
// Save license file
auto license = response_json["license"];
saveLicenseFile(license);
return {true, "", "Activation successful", license};
}
private:
void saveLicenseFile(const nlohmann::json& license) {
std::string license_path = getLicensePath();
std::ofstream file(license_path);
file << license.dump(2);
file.close();
// Set restrictive permissions (Windows)
#ifdef _WIN32
setFilePermissions(license_path, CURRENT_USER_ONLY);
#endif
}
};
💻 Offline Activation¶
When to Use Offline¶
- Air-gapped studio (no internet)
- Firewall blocks license server
- Privacy-conscious users
- Unreliable internet connection
User Flow Diagram¶
┌─────────────────────────────────────────────────────────────┐
│ OFFLINE ACTIVATION │
└─────────────────────────────────────────────────────────────┘
User launches plugin
↓
[No license found]
↓
┌──────────────────────────────┐
│ Enter License Key │
│ XXXX-XXXX-XXXX-XXXX │
│ │
│ [Activate Offline] <-- Click │
└──────────────────────────────┘
↓
Plugin generates machine fingerprint
↓
┌────────────────────────────────────┐
│ Copy this code: │
│ │
│ XXXX-XXXX-XXXX-XXXX (license key) │
│ a1b2c3d4e5f6 (machine ID) │
│ │
│ Or save to file: [Save .txt] │
└────────────────────────────────────┘
↓
User goes to activation website
(on different device with internet)
↓
https://audiolab.com/activate-offline
↓
┌──────────────────────────────┐
│ Paste: │
│ License Key: XXXX-XXXX... │
│ Machine ID: a1b2c3d4e5f6 │
│ │
│ [Generate License File] │
└──────────────────────────────┘
↓
Server validates & generates license.json
↓
User downloads license.json
↓
┌──────────────────────────────┐
│ In plugin: │
│ │
│ [Import License File] │
│ │
│ Select: license.json │
└──────────────────────────────┘
↓
Plugin verifies signature
↓
✅ ACTIVATION COMPLETE
Offline Activation Website¶
URL: https://audiolab.com/activate-offline
<!DOCTYPE html>
<html>
<head>
<title>AudioLab Offline Activation</title>
</head>
<body>
<h1>Offline License Activation</h1>
<form id="activationForm">
<label>License Key:</label>
<input type="text" id="licenseKey" placeholder="XXXX-XXXX-XXXX-XXXX" required>
<label>Machine ID:</label>
<input type="text" id="machineId" placeholder="a1b2c3d4e5f6" required>
<label>Product:</label>
<select id="product">
<option value="audiolab-pro">AudioLab Pro</option>
</select>
<button type="submit">Generate License File</button>
</form>
<div id="result"></div>
<script>
document.getElementById('activationForm').addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch('/api/v1/activate-offline', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
license_key: document.getElementById('licenseKey').value,
machine_id: document.getElementById('machineId').value,
product: document.getElementById('product').value
})
});
const data = await response.json();
if (data.success) {
// Download license file
const blob = new Blob([JSON.stringify(data.license, null, 2)],
{type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'license.json';
a.click();
} else {
alert('Error: ' + data.message);
}
});
</script>
</body>
</html>
Plugin Offline Import¶
class OfflineActivation {
public:
struct Result {
bool success;
std::string error_message;
};
Result importLicenseFile(const std::string& file_path) {
// Read license file
std::ifstream file(file_path);
if (!file.is_open()) {
return {false, "Cannot open license file"};
}
nlohmann::json license;
try {
file >> license;
} catch (const std::exception& e) {
return {false, "Invalid license file format"};
}
// Verify signature
LicenseValidator validator;
if (!validator.verifySignature(license)) {
return {false, "Invalid license signature"};
}
// Check machine ID matches
std::string current_machine_id = MachineFingerprint::generate();
std::string license_machine_id = license["machine_id"];
if (current_machine_id != license_machine_id) {
return {false, "License not valid for this machine"};
}
// Check expiration
if (isExpired(license)) {
return {false, "License has expired"};
}
// Save to local storage
saveLicenseFile(license);
return {true, ""};
}
};
🔄 Deactivation / Transfer¶
Why Allow Transfers?¶
- User upgrades computer
- User replaces motherboard (changes machine ID)
- User sells old computer
- User switches DAW on different machine
Without easy transfers → User feels locked in → Hostile experience
Self-Service Deactivation¶
From Plugin¶
┌─────────────────────────────────┐
│ License Status: ACTIVE │
│ │
│ Machine: Desktop PC │
│ Activated: 2025-01-15 │
│ │
│ [Deactivate This Machine] │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Are you sure? │
│ │
│ This will free up a seat for │
│ activation on another machine. │
│ │
│ [Yes, Deactivate] [Cancel] │
└─────────────────────────────────┘
↓
Send to server:
- License key
- Machine ID
- Deactivate request
↓
Server removes seat
↓
Plugin deletes local license
↓
✅ Deactivation complete
From Website¶
URL: https://audiolab.com/account/licenses
┌──────────────────────────────────────────────────┐
│ My Licenses │
├──────────────────────────────────────────────────┤
│ AudioLab Pro - XXXX-XXXX-XXXX-XXXX │
│ │
│ Seats: 2 / 2 used │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Seat 1: Desktop PC (Windows) │ │
│ │ Activated: 2025-01-15 │ │
│ │ Last used: 2025-10-03 │ │
│ │ [Deactivate] │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Seat 2: Laptop (macOS) │ │
│ │ Activated: 2025-03-20 │ │
│ │ Last used: 2025-09-28 │ │
│ │ [Deactivate] │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
Transfer Policy¶
Generous Limits¶
┌────────────────────────────────────────────┐
│ Transfer Allowance (Per Year) │
├────────────────────────────────────────────┤
│ Self-service transfers: 3 free │
│ Additional transfers: Contact support │
│ (usually approved) │
│ │
│ Cooldown period: 24 hours │
│ (prevents abuse) │
└────────────────────────────────────────────┘
API Implementation¶
POST /api/v1/deactivate
// Request
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"machine_id": "a1b2c3d4e5f6",
"reason": "upgrading_computer" // Optional analytics
}
// Response (Success)
{
"success": true,
"message": "Seat deactivated successfully",
"seats_remaining": 2,
"seats_used": 1,
"transfers_used_this_year": 1,
"transfers_remaining": 2
}
// Response (Limit Exceeded)
{
"success": false,
"error_code": "TRANSFER_LIMIT_EXCEEDED",
"message": "You have used all 3 free transfers this year. Please contact support for assistance.",
"support_url": "https://audiolab.com/support",
"transfers_used_this_year": 3
}
Server-Side Logic¶
from datetime import datetime, timedelta
def deactivate_seat(license_key: str, machine_id: str, user_id: str):
# Find license
license = db.licenses.find_one({"key": license_key})
if not license:
return {"success": False, "error": "LICENSE_NOT_FOUND"}
# Find seat
seat = next((s for s in license["seats"] if s["machine_id"] == machine_id), None)
if not seat:
return {"success": False, "error": "SEAT_NOT_FOUND"}
# Check transfer limits
this_year = datetime.now().year
transfers = db.transfers.count({
"license_key": license_key,
"year": this_year
})
if transfers >= 3:
return {
"success": False,
"error_code": "TRANSFER_LIMIT_EXCEEDED",
"transfers_used": transfers
}
# Remove seat
license["seats"].remove(seat)
db.licenses.update_one({"key": license_key}, {"$set": {"seats": license["seats"]}})
# Log transfer
db.transfers.insert_one({
"license_key": license_key,
"user_id": user_id,
"machine_id": machine_id,
"year": this_year,
"timestamp": datetime.now()
})
return {
"success": True,
"seats_remaining": license["max_seats"],
"seats_used": len(license["seats"]),
"transfers_used_this_year": transfers + 1,
"transfers_remaining": 3 - (transfers + 1)
}
📋 Activation UI/UX Best Practices¶
Clear Messaging¶
❌ Bad:
✅ Good:
License Activation Failed
Your license key could not be validated.
Possible reasons:
• No internet connection (try offline activation)
• License key expired (renew at audiolab.com)
• Too many activations (deactivate old machine)
Need help? Visit audiolab.com/support
Progress Indication¶
void showActivationProgress() {
showDialog({
.title = "Activating License",
.message = "Contacting license server...",
.progress = 0.3,
.cancelable = true
});
// After network request
updateDialog({
.message = "Validating license...",
.progress = 0.6
});
// After validation
updateDialog({
.message = "Saving license...",
.progress = 0.9
});
// Complete
showDialog({
.title = "Success!",
.message = "AudioLab Pro is now activated.",
.icon = "✅",
.autoClose = 3000
});
}
🔗 Related Documents¶
- LICENSING_PHILOSOPHY.md - Overall licensing strategy
- LICENSE_VALIDATION.md - Technical validation details
- UX patterns for license activation
- Error handling best practices