License Activation API
Protect your commercial plugins with BeatConnect’s activation system. Generate license keys, control activations per machine, and validate licenses in real-time.
Overview
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Customer │ │ Your Plugin │ │ BeatConnect │
│ Purchases │────▶│ Requests │────▶│ Validates │
│ Plugin │ │ Activation │ │ License │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Activation │ │ Database │
│ Confirmed │◀────│ Updated │
└─────────────────┘ └─────────────────┘
Key Concepts
| Term | Description |
|---|---|
| Activation Code | The license key given to customers (UUID format) |
| Activation | A specific machine using the license |
| Machine ID | Unique identifier for a computer |
| Max Activations | How many machines can use one license |
Quick Start
1. Enable Activation in Build
{
"flags": {
"enableActivationKeys": true
}
}2. Generate Test Keys
curl -X POST "https://api.beatconnect.com/functions/v1/plugin-activation/generate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"max_activations": 2,
"notes": "Development testing"
}'Response:
{
"code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"key_type": "project_test",
"max_activations": 2,
"created_at": "2025-01-15T10:30:00Z"
}3. Integrate in Your Plugin
See Plugin Integration below.
API Endpoints
Activate License
POST /plugin-activation/activate
Activate a license on a new machine.
curl -X POST "https://api.beatconnect.com/functions/v1/plugin-activation/activate" \
-H "Content-Type: application/json" \
-d '{
"code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"machine_id": "unique-machine-identifier"
}'Success Response (200):
{
"success": true,
"activations": 1,
"max_activations": 2,
"remaining": 1
}Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | invalid_code | Code format invalid |
| 404 | code_not_found | Code doesn’t exist |
| 403 | code_revoked | License was revoked |
| 403 | max_activations_reached | No more slots available |
Deactivate License
POST /plugin-activation/deactivate
Free up an activation slot.
curl -X POST "https://api.beatconnect.com/functions/v1/plugin-activation/deactivate" \
-H "Content-Type: application/json" \
-d '{
"code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"machine_id": "unique-machine-identifier"
}'Success Response (200):
{
"success": true,
"activations": 0,
"max_activations": 2,
"message": "Machine deactivated successfully"
}Validate License
POST /plugin-activation/validate
Check if a license is valid on this machine. Lightweight, designed for frequent calls.
curl -X POST "https://api.beatconnect.com/functions/v1/plugin-activation/validate" \
-H "Content-Type: application/json" \
-d '{
"code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"machine_id": "unique-machine-identifier"
}'Success Response (200):
{
"valid": true
}Invalid Response (200):
{
"valid": false,
"reason": "not_activated"
}Possible reasons:
not_activated— Machine not activated for this codecode_revoked— License was revokedcode_not_found— Code doesn’t existdeactivated— Previously activated but now deactivated
Check License Status
GET /plugin-activation/status/:code
Public endpoint to check license status (no auth required).
curl "https://api.beatconnect.com/functions/v1/plugin-activation/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890"Response (200):
{
"activations": 1,
"max_activations": 2,
"available": 1,
"is_revoked": false
}Generate Keys (Authenticated)
POST /plugin-activation/generate
Generate test/admin keys for your project.
curl -X POST "https://api.beatconnect.com/functions/v1/plugin-activation/generate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"max_activations": 2,
"notes": "Beta tester - John"
}'Limits:
- Standard creators: 3 test keys per project
- Admins: Unlimited
List Project Keys (Authenticated)
GET /plugin-activation/project/:projectId/keys
View all test keys for a project.
curl "https://api.beatconnect.com/functions/v1/plugin-activation/project/YOUR_PROJECT_ID/keys" \
-H "Authorization: Bearer YOUR_TOKEN"Response:
{
"keys": [
{
"code": "a1b2c3d4-...",
"key_type": "project_test",
"max_activations": 2,
"current_activations": 1,
"is_revoked": false,
"notes": "Beta tester",
"created_at": "2025-01-15T10:30:00Z"
}
]
}Plugin Integration
C++ Integration (JUCE)
Machine ID Generation
String generateMachineId()
{
// Combine hardware identifiers for uniqueness
String systemId;
#if JUCE_MAC
// Use Mac's hardware UUID
systemId = SystemStats::getMachineIdentifiers().joinIntoString("-");
#elif JUCE_WINDOWS
// Use Windows machine GUID + volume serial
systemId = SystemStats::getMachineIdentifiers().joinIntoString("-");
#endif
// Hash for privacy
return SHA256(systemId.toUTF8()).toHexString();
}Activation Manager Class
class ActivationManager
{
public:
ActivationManager() : machineId(generateMachineId()) {}
// Activate with a license code
Result activate(const String& code)
{
URL url("https://api.beatconnect.com/functions/v1/plugin-activation/activate");
DynamicObject::Ptr json = new DynamicObject();
json->setProperty("code", code);
json->setProperty("machine_id", machineId);
auto response = url.withPOSTData(JSON::toString(json))
.withExtraHeaders("Content-Type: application/json")
.readEntireTextStream();
auto result = JSON::parse(response);
if (result.getProperty("success", false))
{
saveActivationCode(code);
return Result::ok();
}
return Result::fail(result.getProperty("error", "Activation failed").toString());
}
// Validate current activation
bool validate()
{
String code = loadActivationCode();
if (code.isEmpty()) return false;
URL url("https://api.beatconnect.com/functions/v1/plugin-activation/validate");
DynamicObject::Ptr json = new DynamicObject();
json->setProperty("code", code);
json->setProperty("machine_id", machineId);
auto response = url.withPOSTData(JSON::toString(json))
.withExtraHeaders("Content-Type: application/json")
.readEntireTextStream();
auto result = JSON::parse(response);
return result.getProperty("valid", false);
}
// Deactivate from this machine
bool deactivate()
{
String code = loadActivationCode();
if (code.isEmpty()) return false;
URL url("https://api.beatconnect.com/functions/v1/plugin-activation/deactivate");
DynamicObject::Ptr json = new DynamicObject();
json->setProperty("code", code);
json->setProperty("machine_id", machineId);
auto response = url.withPOSTData(JSON::toString(json))
.withExtraHeaders("Content-Type: application/json")
.readEntireTextStream();
auto result = JSON::parse(response);
if (result.getProperty("success", false))
{
clearActivationCode();
return true;
}
return false;
}
private:
String machineId;
void saveActivationCode(const String& code)
{
// Save to user's app data folder
auto file = File::getSpecialLocation(File::userApplicationDataDirectory)
.getChildFile("YourCompany/YourPlugin/license.dat");
file.create();
file.replaceWithText(code);
}
String loadActivationCode()
{
auto file = File::getSpecialLocation(File::userApplicationDataDirectory)
.getChildFile("YourCompany/YourPlugin/license.dat");
return file.existsAsFile() ? file.loadFileAsString().trim() : String();
}
void clearActivationCode()
{
auto file = File::getSpecialLocation(File::userApplicationDataDirectory)
.getChildFile("YourCompany/YourPlugin/license.dat");
file.deleteFile();
}
};Using in Your Plugin
class MyPluginProcessor : public AudioProcessor
{
public:
MyPluginProcessor()
{
// Check activation on load
if (!activationManager.validate())
{
isActivated = false;
// Show activation dialog or limit functionality
}
else
{
isActivated = true;
}
}
void processBlock(AudioBuffer<float>& buffer, MidiBuffer&) override
{
if (!isActivated)
{
// Demo mode: silence after 30 seconds
if (demoTimer.getMillisecondCounter() > 30000)
{
buffer.clear();
return;
}
}
// Normal processing...
}
private:
ActivationManager activationManager;
bool isActivated = false;
Time demoTimer;
};Offline Activation
For users without internet access:
// Generate offline activation request
String generateOfflineRequest(const String& code)
{
DynamicObject::Ptr request = new DynamicObject();
request->setProperty("code", code);
request->setProperty("machine_id", machineId);
request->setProperty("timestamp", Time::getCurrentTime().toISO8601(true));
// User sends this to support
return Base64::toBase64(JSON::toString(request));
}
// Apply offline activation response
bool applyOfflineActivation(const String& response)
{
// Response contains signed activation token
auto decoded = Base64::fromBase64(response);
auto json = JSON::parse(decoded);
// Verify signature and save
if (verifySignature(json))
{
saveActivationCode(json.getProperty("code", "").toString());
return true;
}
return false;
}Best Practices
Do
- Cache validation results — Don’t validate on every process block
- Handle offline gracefully — Allow temporary offline use
- Provide clear error messages — Help users understand activation issues
- Allow deactivation — Let users move licenses between machines
- Use secure storage — Don’t store codes in plain text
Don’t
- Don’t validate too frequently — Once per session or hour is enough
- Don’t block the audio thread — Do network calls on a background thread
- Don’t be overly restrictive — Reasonable limits keep customers happy
- Don’t rely solely on activation — Determined crackers will bypass it
Demo Mode Implementation
enum class LicenseState
{
NotChecked,
Activated,
DemoMode,
DemoExpired
};
class DemoManager
{
public:
static constexpr int DEMO_DAYS = 14;
static constexpr int DEMO_SECONDS_PER_SESSION = 30 * 60; // 30 minutes
LicenseState getState()
{
if (isActivated()) return LicenseState::Activated;
auto firstRun = getFirstRunDate();
auto daysSinceInstall = (Time::getCurrentTime() - firstRun).inDays();
if (daysSinceInstall > DEMO_DAYS)
return LicenseState::DemoExpired;
return LicenseState::DemoMode;
}
int getDaysRemaining()
{
auto firstRun = getFirstRunDate();
auto daysSinceInstall = (Time::getCurrentTime() - firstRun).inDays();
return jmax(0, DEMO_DAYS - (int)daysSinceInstall);
}
};Purchase Integration
When a customer purchases through BeatConnect:
- Order created → Activation code automatically generated
- Customer receives → Code in purchase confirmation email
- Plugin activation → Customer enters code in your plugin
- License tracked → Activations managed in your dashboard
Webhook for Sales
{
"event": "purchase.completed",
"data": {
"order_id": "order_123",
"customer_email": "customer@example.com",
"product_id": "your-plugin-id",
"activation_code": "a1b2c3d4-..."
}
}Management Dashboard
In Creator Portal → Your Product → Licenses:
- View all activation codes
- See activation count per code
- Revoke compromised licenses
- Generate promotional codes
- Export license data
Troubleshooting
”Activation failed”
Common causes:
- Network blocked — Check firewall settings
- Invalid code — Verify code format (UUID)
- Max activations reached — User needs to deactivate another machine
”Validation failed after working”
Common causes:
- License revoked — Check dashboard
- Machine ID changed — Major hardware change
- API timeout — Network issues
Machine ID Changing
If machine ID changes (hardware upgrade), user can:
- Deactivate from old machine ID (if they saved it)
- Contact support to reset activations
- Use offline activation process
Security Considerations
Rate Limiting
All endpoints are rate-limited:
- Activate: 10 requests/minute per IP
- Validate: 60 requests/minute per IP
- Generate: 10 requests/hour per user
Code Format
Activation codes use UUID v4:
- 36 characters with hyphens
- Example:
a1b2c3d4-e5f6-7890-abcd-ef1234567890 - ~5.3 × 10^36 possible combinations
Revocation
Revoked codes:
- Cannot be activated
- Existing activations are invalidated
- Validation returns
code_revoked