License your software with a heartbeat
elm is evoleap's cloud licensing service. Your app registers once with a license key, then keeps a session alive while it runs. Seats, features, component tokens and offline use are set per license in the elm portal, not in your code.
Quick Start
Get up and running with elm licensing in your software. Choose your programming language and follow the step-by-step integration guide below.
The SDKs talk to the elm client licensing API (the released .NET, Java, C++, JavaScript and Ruby SDKs use API v3.4; the Python SDK uses v3.2), but they do not all cover the same product types:
- C# / .NET (
evoleap.Licensing), the reference implementation: desktop apps (ControlManager), servers and web apps (ServerControlManager+UserControlManager). - Java (
com.evoleap.licensing): desktop and server/web app control managers. - Python (
evoleap_licensing): desktop apps only (ControlManager). - JavaScript / TypeScript (
evoleap-licensing, Node.js): servers and web apps only (server instance + per-user sessions). There is no desktop control manager. - C++ (
evoleap-licensingvcpkg port, Windows x64): desktop (ControlManager) and server instance (ServerControlManager). - Ruby (
evoleap-licensinggem): server-side only, for Ruby and Rails web apps (server instance + per-user sessions). There is no desktop control manager.
.NET Integration
Step 1: Install the Package
Add the evoleap.Licensing package to your project (targets .NET Framework 4.7.2, .NET Core 3.1 and .NET 6.0+):
Install-Package evoleap.Licensing
Or via .NET CLI:
dotnet add package evoleap.Licensing
Step 2: Add Using Statements
Import the necessary namespaces in your code:
using evoleap.Licensing;
using evoleap.Licensing.Desktop; // For desktop apps (ControlManager)
// or
using evoleap.Licensing.Server; // For server/web apps (ServerControlManager, UserControlManager)
Step 3: Initialize the Control Manager
Create and configure your licensing control manager:
// For Desktop Applications
var productId = new Guid("your-product-guid");
// Public key file downloaded from the elm portal for your product
var publicKey = PublicKeyFile.ReadFrom(@"path\to\public-key-file");
var userIdentity = await ActiveDirectoryBasedUserIdentity.GetCurrentAsync();
var instanceIdentity = await new InstanceIdentity().AddAllHardwareKeysAsync();
var controlManager = new ControlManager(
productId,
new Version("1.0.0"),
publicKey,
userIdentity,
instanceIdentity
// optional: controlStrategy, savedState, sessionState
);
// Persist controlManager.ValidationState and controlManager.SessionState securely
// and pass them back in as savedState/sessionState on the next launch.
Step 4: Register Your Application
Register the application instance with elm:
if (!controlManager.ValidationState.Registered)
{
var userInfo = new UserInfo("User Name", "user@company.com");
var registrationResult = await controlManager.RegisterAsync("YOUR-LICENSE-KEY", userInfo);
if (!registrationResult.Success)
{
// Handle registration failure
throw new Exception($"Registration failed: {registrationResult.ErrorMessage}");
}
}
Step 5: Validate Sessions
Add session validation to your application startup:
var sessionValidity = await controlManager.ValidateSessionAsync();
if (!sessionValidity.IsValid)
{
// Handle invalid session
throw new UnauthorizedAccessException($"License invalid: {sessionValidity.InvalidReason}");
}
// Features enabled on the license
var features = controlManager.ValidationState.Features;
// Your application logic here
RunYourApplication();
// End session when application closes
await controlManager.EndSessionAsync();
Java Integration
Step 1: Add Maven Dependency
Add the dependency to your pom.xml:
<dependency>
<groupId>com.evoleap.licensing</groupId>
<artifactId>com.evoleap.licensing</artifactId>
<version>1.1.0</version>
</dependency>
Or for Gradle in build.gradle:
implementation 'com.evoleap.licensing:com.evoleap.licensing:1.1.0'
Step 2: Import Classes
Import the necessary licensing classes (note the capitalized sub-packages):
import com.evoleap.licensing.*; // identities, results, PublicKeyFile
import com.evoleap.licensing.Utilities.Version;
import com.evoleap.licensing.Desktop.ControlManager; // For desktop apps
// or
import com.evoleap.licensing.Server.ServerControlManager; // For servers / web apps
Step 3: Initialize Control Manager
Set up your licensing control manager:
// For Desktop Applications
UUID productId = UUID.fromString("your-product-guid");
// Public key file downloaded from the elm portal for your product
PublicKeyFile publicKey = PublicKeyFile.readFrom("path/to/public-key-file");
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity.getCurrent();
InstanceIdentity instanceIdentity =
HardwareBasedInstanceIdentity.addAllHardwareKeys(new InstanceIdentity());
ControlManager controlManager = new ControlManager(
productId,
new Version("1.0.0"),
publicKey,
userIdentity,
instanceIdentity,
null, // controlStrategy (default)
null, // savedState from a previous launch
null // sessionState from a previous launch
);
Step 4: Register Application
Register with the elm licensing server. The ...Async methods return a
com.evoleap.licensing.Utilities.Task<T>, which implements
Future<T>:
UserInfo userInfo = new UserInfo("User Name", "user@company.com");
RegistrationResult result = controlManager.registerAsync("YOUR-LICENSE-KEY", userInfo).get();
if (!result.getSuccess()) {
throw new RuntimeException("Registration failed: " + result.getErrorMessage());
}
Step 5: Implement Session Validation
Add licensing validation to your application flow:
SessionValidity sessionValidity = controlManager.validateSessionAsync(null).get();
if (!sessionValidity.getIsValid()) {
throw new SecurityException("License invalid: " + sessionValidity.getInvalidReason());
}
// Features enabled on the license: controlManager.getValidationState().getFeatures()
// Run your application
runYourApplication();
// Clean up session
controlManager.endSessionAsync().get();
Python Integration
Step 1: Install the Package
Install the elm licensing package (Python 3; depends on pycryptodome and
pytz):
pip install evoleap_licensing
Or add to requirements.txt:
evoleap_licensing>=1.1.4
Step 2: Import Modules
Import the necessary licensing modules:
from evoleap_licensing.desktop.ControlManager import ControlManager # Desktop apps only
from evoleap_licensing.UserInfo import UserInfo
from evoleap_licensing.UserIdentity import UserIdentity
from evoleap_licensing.InstanceIdentity import InstanceIdentity, InstanceIdentityValue
from uuid import UUID, getnode
import os
import socket
Step 3: Setup Control Manager
Initialize your licensing control manager. The Python SDK has no built-in hardware fingerprinting, so you build the instance identity from values you choose:
# For Desktop Applications
product_id = UUID('your-product-guid')
with open('public-key.pem') as f:
public_key = f.read() # PEM text of your product's public key
# POSIX example; see "Creating User Identities" below for Windows
user_identity = UserIdentity({'uid': str(os.getuid())})
instance_identity = InstanceIdentity({
'mac': InstanceIdentityValue(format(getnode(), '012x')),
'hostname': InstanceIdentityValue(socket.gethostname(), case_sensitive=False),
})
control_manager = ControlManager(
product_id,
"1.0.0",
public_key,
user_identity,
instance_identity
# optional: strategy=..., saved_state=..., session_state=...
)
Step 4: Register Your Application
Register the application instance:
if not control_manager.State.Registered:
user_info = UserInfo("User Name", "user@company.com")
registration_result = control_manager.Register("YOUR-LICENSE-KEY", user_info)
if not registration_result.Success:
raise Exception(f"Registration failed: {registration_result.ErrorMessage}")
# Persist control_manager.State securely (encrypted) and pass it back as saved_state next time
Step 5: Add Session Validation
Implement licensing validation in your application:
session_validity = control_manager.ValidateSession()
if not session_validity.IsValid:
raise PermissionError(f"License invalid: {session_validity.InvalidReason}")
features = control_manager.State.Features
# Your application logic
run_your_application()
# Clean up session
control_manager.EndSession()
JavaScript/Node.js Integration
The JavaScript SDK is functional: each call takes a control manager object and returns an
updated one (in result.state) that you persist and pass to the next call. It
licenses a server instance plus per-user sessions; there is no desktop control manager.
Step 1: Install the Package
Install via npm:
npm install evoleap-licensing
Or add to package.json:
{
"dependencies": {
"evoleap-licensing": "^0.7.108"
}
}
Step 2: Import the Module
The package exports the server, user, hardware,
instance and state namespaces:
// ES Modules / TypeScript
import * as elm from 'evoleap-licensing';
// CommonJS
const elm = require('evoleap-licensing');
Step 3: Initialize Control Manager
Set up the server control manager and the server's instance identity:
const fs = require('fs');
const productId = 'your-product-guid';
const version = '1.0.0';
const publicKey = fs.readFileSync('public-key.pem', 'utf8');
// Initial server state; afterwards, persist scm.state and restore it here
const initialServerState = {
registered: false,
registeredAt: '',
failedRegistrationTimes: [],
failedValidationTimes: [],
featureGroups: [],
features: [],
firstLaunchTime: '',
gracePeriodForValidationFailures: 0,
instanceId: '',
lastSuccessfulValidationTime: '',
lastValidationStatus: 0,
};
let scm = elm.server.getServerControlManager(productId, version, publicKey, initialServerState);
// Host name, CPU id, volume hash and network info (Node.js)
const instanceIdentity = await elm.hardware.addAllHardwareKeys({
Version: 1,
MaximumMismatchCount: 1,
Values: new Map(),
});
Step 4: Register Server Instance
Register your server with elm:
const registration = await elm.server.registerServer('YOUR-LICENSE-KEY', scm, instanceIdentity);
if (!registration.success) {
throw new Error(`Registration failed: ${registration.message}`);
}
scm = registration.state; // persist scm.state
const instanceValidity = await elm.server.validateInstance(scm, instanceIdentity);
if (!instanceValidity.isValid) {
throw new Error(`Instance invalid: ${elm.state.InvalidReason[instanceValidity.InvalidReason]}`);
}
scm = instanceValidity.state;
Step 5: Implement Session Validation
Register each user once, then validate a session for them:
// Identity of the signed-in user, from your own authentication system
const userIdentity = { version: 1, data: new Map([['accountId', String(account.id)]]) };
let ucm = elm.user.getUserControlManager(scm.state.instanceId, scm);
// For a returning user, restore their saved state:
// elm.user.getUserControlManager(scm.state.instanceId, scm, savedUserState, savedSessionState)
const userRegistration = await elm.user.register(
ucm,
{ name: account.name, email: account.email, additionalData: new Map() },
userIdentity
);
ucm = userRegistration.state;
const sessionValidity = await elm.user.validateSession(ucm, userIdentity);
if (!sessionValidity.isValid) {
throw new Error(`License invalid: ${elm.state.InvalidReason[sessionValidity.InvalidReason]}`);
}
ucm = sessionValidity.state; // persist ucm.state and ucm.sessionState for this user
const features = ucm.state.features;
// End the session when the user signs out
ucm = await elm.user.endSession(ucm);
C++ Integration
Step 1: Install via vcpkg
The evoleap-licensing port (Windows x64, MSBuild) is published in evoleap's own
vcpkg registry, not the public vcpkg catalog. Add the registry to
vcpkg-configuration.json:
{
"registries": [
{
"kind": "git",
"repository": "https://github.com/evoleap/vcpkg-registry",
"baseline": "<latest commit of evoleap/vcpkg-registry>",
"packages": [ "evoleap-licensing" ]
}
]
}
Then add the dependency to your vcpkg.json manifest:
vcpkg add port evoleap-licensing
The port installs the headers and the evoleap.licensing library; with Visual
Studio's vcpkg manifest integration they are picked up automatically.
Step 2: Include Headers
Include the necessary licensing headers:
#include "ControlManager.h"
#include "UserInfo.h"
#include "UserIdentity.h"
#include "ActiveDirectoryBasedUserIdentity.h"
#include "InstanceIdentity.h"
#include "HardwareBasedInstanceIdentity.h"
#include "PublicKeyFile.h"
#include "Version.h"
#include <iostream>
#include <string>
Step 3: Initialize Control Manager
Set up your licensing control manager:
// For Desktop Applications
uuids::uuid productId = uuids::uuid::from_string("your-product-guid").value();
Version version("1.0.0");
// Contents of the public key file downloaded from the elm portal
PublicKeyFile* publicKey = PublicKeyFile::Create(publicKeyText);
// Current Windows / Active Directory user
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity::GetCurrent();
// Host name, CPU id, volume hash and network info
InstanceIdentity instanceIdentity;
HardwareBasedInstanceIdentity::AddAllHardwareKeys(instanceIdentity);
// Create control manager (optional: ControlStrategy*, saved IValidationState*, IValidatedSessionState*)
ControlManager controlManager(
productId,
version,
publicKey,
userIdentity,
instanceIdentity
);
Step 4: Register Application
Register your application with elm:
UserInfo userInfo("User Name", "user@company.com");
auto registrationResult = controlManager.Register("YOUR-LICENSE-KEY", userInfo);
if (!registrationResult.get_Success()) {
std::cerr << "Registration failed: " << registrationResult.get_ErrorMessage() << std::endl;
return -1;
}
std::cout << "Registration successful!" << std::endl;
Step 5: Implement Session Validation
Add licensing validation to your application:
SessionValidity sessionValidity = controlManager.ValidateSession();
if (!sessionValidity.IsValid) {
std::cerr << "License invalid: " << sessionValidity.InvalidReason << std::endl;
return -1;
}
std::cout << "License is valid!" << std::endl;
// Your application logic here
runYourApplication();
// End session
bool endResult = controlManager.EndSession();
if (endResult) {
std::cout << "Session ended successfully" << std::endl;
}
Ruby / Rails Integration
The Ruby SDK is server-side only: it licenses a server instance (your Ruby or Rails deployment) and per-user sessions on that instance. It has no desktop control manager and no hardware weights or mismatch tolerance settings. It requires Ruby 3.1+ and uses API v3.4.
Step 1: Install the Gem
Add the gem to your Gemfile (the library file is
evoleap_licensing, so tell Bundler what to require):
gem "evoleap-licensing", require: "evoleap_licensing"
Or install it directly:
gem install evoleap-licensing
Step 2: Require and Configure
For example in config/initializers/evoleap_licensing.rb:
require "evoleap_licensing"
EvoleapLicensing.configure do |config|
config.host = "https://elm.evoleap.com" # default
config.timeout = 60 # HTTP timeout in seconds
end
Step 3: Register the Server Instance
Do this once per deployment and persist the server state:
public_key = File.read(Rails.root.join("config", "elm_public_key.pem"))
scm = EvoleapLicensing::ServerControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
public_key: public_key
)
identity = EvoleapLicensing::InstanceIdentity.from_hardware
result = scm.register(license_key: "YOUR-LICENSE-KEY", instance_identity: identity)
if result.success?
File.write("config/server_state.json", scm.server_state.to_h.to_json)
else
raise "Registration failed: #{result.error_message}"
end
Step 4: Register Users
When a user first uses your application:
server_state = EvoleapLicensing::ServerState.new(JSON.parse(File.read("config/server_state.json")))
ucm = EvoleapLicensing::UserControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
instance_id: server_state.instance_guid,
public_key: public_key
)
user_identity = EvoleapLicensing::UserIdentity.new("uid" => current_user.id.to_s)
result = ucm.register(
user_info: { name: current_user.name, email: current_user.email },
user_identity: user_identity
)
current_user.update!(elm_user_state: ucm.user_state.to_h.to_json) if result.success?
Step 5: Validate Sessions
On each request (for example in a before_action), restore the user's state and
validate:
ucm = EvoleapLicensing::UserControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
instance_id: server_state.instance_guid,
public_key: public_key,
user_state: EvoleapLicensing::UserState.new(JSON.parse(current_user.elm_user_state)),
session_state: EvoleapLicensing::SessionState.new(session[:elm_session_state])
)
validity = ucm.validate_session(user_identity: user_identity)
if validity.valid?
current_user.update!(elm_user_state: ucm.user_state.to_h.to_json)
session[:elm_session_state] = ucm.session_state.to_h
features = ucm.session_state.features
else
handle_license_failure(validity.invalid_reason) # e.g. :license_expired, :no_seats_available
end
Preview — API v3.5, not yet released. A forthcoming gem version adds
ucm.session_state.custom_data (license properties) and
scm.validate_user(user_guid:), which checks a user's entitlement for API
calls and background jobs without starting a session or using a seat.
Products and Licenses
elm's licensing architecture is built around a flexible product and license model that supports diverse software deployment scenarios from desktop applications to cloud services.
Products
A product represents your software application with a unique GUID identifier and its own public key. Each product has a product type (Desktop app, Server, Web app or Web API), and owns its features, components and versions.
Licenses
A license is issued to a customer (a company or business unit) for one product and is identified by its license key. Its license type, entitlements (seat counts), restrictions, features and component entitlements determine what the customer may use.
⏰ License Terms
Time-bounded validity periods (start and end dates). A license is active while it has a current term. The commercial model (Perpetual with M&S, SaaS, Lease, Free trial or Freeware) describes how the license is sold.
Product Architecture by Deployment Type
Desktop Products
Use Case: Traditional installable software (CAD, engineering tools,
productivity
apps)
Licensing: User + Instance identity, supports all license types and
components
Control Flow: Registration → Session validation → Feature/component access
→
Session cleanup
Server Products
Use Case: Software installed on customer servers that is licensed per
server instance
Licensing: Instance identity only (ServerControlManager); server
products cannot have components
Control Flow: Instance registration → Instance validation
Web App Products
Use Case: Multi-user web applications, on-premise or hosted
Licensing: Server instance plus per-user registration and sessions
(ServerControlManager + UserControlManager); supports
components
Control Flow: Instance registration → User registration → User session
validation
Web API Products
Use Case: Cloud services, SaaS APIs, consumption-based offerings
Licensing: Consumable token components only, checked out with a
license-scoped API token rather than through a control manager
Control Flow: Token consumption per API call → Usage tracking
License Types
The license type is chosen per license and decides which seat count in the license's entitlement is enforced. Additional restrictions (for example sessions per user, users per instance, or a minimum assignment length before a named-user seat can be reassigned) can be layered on top.
Named User
Limits the number of registered users (identified by user identity). Ideal for professional software with dedicated users.
Concurrent
Limits simultaneous active sessions (optionally counted as unique users). Active offline checkouts count against the limit. Suited to floating licenses in shared environments.
Node-Locked
Limits the number of registered instances, identified by their instance (hardware) identity.
Unrestricted
No seat limit; typically combined with component entitlements for consumption-based licensing.
License Properties (Custom Data) Preview
Preview — API v3.5, not yet released. License properties are not available in the released backend or SDKs; the names below may change before release.
Vendors can attach key-value properties to a license in the elm portal (keys may contain letters,
digits, _, - and .). From client API v3.5 they are returned
as custom_data on successful session and validation responses only, so they can carry
per-customer configuration such as endpoints or keys. The SDKs expose them as
ValidationState.CustomData (.NET), state.customData (JavaScript) and
session_state.custom_data / validate_user(...).custom_data (Ruby).
Instances and Users
Understanding how elm identifies and manages software instances and user identities across different deployment scenarios.
Instance Identity Architecture
Hardware Fingerprinting
An instance identity is a set of weighted key-value pairs. The built-in hardware helpers (.NET, Java, C++, JavaScript) add the host name, CPU identifier, a volume hash, and the IP and MAC addresses.
Mismatch Tolerance
Each identity has a maximum mismatch count (default 1), so a hardware change such as a new network adapter does not invalidate the instance while license sharing is still detected.
Hardware Changes
Changes within the tolerance are accepted and the stored identifier is updated and
recorded. Larger changes report InconsistentRegistration; an administrator can
then disable or remove the old instance so the machine can register again.
Creating Instance Identities
Most SDKs provide built-in methods to generate hardware-based instance identities, and all of them let you create custom identities from key-value pairs for specific deployment scenarios.
Instance Identity (.NET)
// Option 1: All hardware keys (recommended for most cases):
// host name, CPU identifier, volume hash, IP address and MAC addresses
var instanceIdentity = new InstanceIdentity().AddAllHardwareKeys();
// or without blocking the UI thread:
// var instanceIdentity = await new InstanceIdentity().AddAllHardwareKeysAsync();
// Option 2: Selected hardware keys (extension methods in HardwareBasedInstanceIdentity)
var selectiveIdentity = new InstanceIdentity()
.AddHostName()
.AddCpuIdentifier()
.AddVolumeHash()
.AddNetworkInfo(); // IP address and MAC addresses
// Option 3: Custom key-value pairs for specific environments
var customInstanceIdentity = new InstanceIdentity();
customInstanceIdentity.Values.Add("serverId", new InstanceIdentityValue("PROD-SERVER-001"));
customInstanceIdentity.Values.Add("datacenter", new InstanceIdentityValue("us-west-2"));
// value, caseSensitive, weight
customInstanceIdentity.Values.Add("hostname", new InstanceIdentityValue(Environment.MachineName, false, 1));
// MaximumMismatchCount defaults to 1 (one value may change); it is read-only
// and can only be set through the InstanceIdentity constructor.
Instance Identity (Java)
// Option 1: All hardware keys (recommended for most cases)
InstanceIdentity instanceIdentity =
HardwareBasedInstanceIdentity.addAllHardwareKeys(new InstanceIdentity());
// Option 2: Selected hardware keys
InstanceIdentity selectiveIdentity = new InstanceIdentity();
HardwareBasedInstanceIdentity.addCpuIdentifier(selectiveIdentity);
HardwareBasedInstanceIdentity.addVolumeHash(selectiveIdentity);
HardwareBasedInstanceIdentity.addNetworkInfo(selectiveIdentity); // IP and MAC addresses
// Option 3: Custom key-value pairs, with an explicit mismatch tolerance
Map<String, InstanceIdentityValue> values = new HashMap<>();
values.put("serverId", new InstanceIdentityValue("PROD-SERVER-001"));
values.put("datacenter", new InstanceIdentityValue("us-west-2"));
values.put("hostname", new InstanceIdentityValue(InetAddress.getLocalHost().getHostName(), false, 1));
InstanceIdentity customInstanceIdentity = new InstanceIdentity(
values,
InstanceIdentity.DefaultVersion,
1 // maximum mismatch count: allow 1 value to change
);
Instance Identity (Python)
import socket
import platform
from uuid import getnode
from evoleap_licensing.InstanceIdentity import InstanceIdentity, InstanceIdentityValue
# The Python SDK has no built-in hardware fingerprinting; build the identity
# from stable machine values you choose.
values = {
"mac": InstanceIdentityValue(format(getnode(), "012x")),
"hostname": InstanceIdentityValue(socket.gethostname(), case_sensitive=False),
"platform": InstanceIdentityValue(platform.system()),
"serverId": InstanceIdentityValue("PROD-SERVER-001", weight=2),
}
# Mismatch tolerance (default 1) is set in the constructor
instance_identity = InstanceIdentity(values, maximum_mismatch_count=1)
Instance Identity (JavaScript/Node.js)
const os = require('os');
const elm = require('evoleap-licensing');
// Option 1: All hardware keys (recommended for most cases)
const instanceIdentity = await elm.hardware.addAllHardwareKeys({
Version: 1,
MaximumMismatchCount: 1,
Values: new Map(),
});
// Option 2: Custom key-value pairs for server environments
const value = (v) => ({ Value: v, CaseSensitive: true, Weight: 1, MaxArrayValueDifference: 1 });
const customInstanceIdentity = {
Version: 1,
MaximumMismatchCount: 1,
Values: new Map([
['serverId', value('PROD-SERVER-001')],
['datacenter', value('us-west-2')],
['hostname', value(os.hostname())],
]),
};
// Option 3: Container-based identity for Docker/Kubernetes
const containerInstanceIdentity = {
Version: 1,
MaximumMismatchCount: 0, // Strict matching for containers
Values: new Map([
['namespace', value(process.env.KUBERNETES_NAMESPACE || 'default')],
['deployment', value(process.env.DEPLOYMENT_NAME || 'standalone')],
]),
};
Instance Identity (C++)
#include "InstanceIdentity.h"
#include "HardwareBasedInstanceIdentity.h"
#include <string>
// Option 1: All hardware keys (recommended for most cases)
InstanceIdentity instanceIdentity;
HardwareBasedInstanceIdentity::AddAllHardwareKeys(instanceIdentity);
// Option 2: Selected hardware keys
InstanceIdentity selectiveIdentity;
HardwareBasedInstanceIdentity::AddCpuIdentifier(selectiveIdentity);
HardwareBasedInstanceIdentity::AddVolumeHash(selectiveIdentity);
HardwareBasedInstanceIdentity::AddNetworkInfo(selectiveIdentity); // IP and MAC addresses
// Option 3: Custom key-value pairs, with an explicit mismatch tolerance
InstanceIdentity customInstanceIdentity(
{
{ "serverId", InstanceIdentityValue(std::string("PROD-SERVER-001")) },
{ "datacenter", InstanceIdentityValue(std::string("us-west-2")) },
},
InstanceIdentity::DefaultVersion,
1 // maximum mismatch count
);
// Values can also be added afterwards:
customInstanceIdentity.get_Values().Add("rack", InstanceIdentityValue(std::string("R12")));
Instance Identity (Ruby)
require "evoleap_licensing"
require "socket"
# Option 1: Values collected from the host platform
instance_identity = EvoleapLicensing::InstanceIdentity.from_hardware
# Option 2: Custom key-value pairs (sent as strings; no per-key weights)
custom_identity = EvoleapLicensing::InstanceIdentity.new(
"serverId" => "PROD-SERVER-001",
"datacenter" => "us-west-2"
)
custom_identity.add("hostname", Socket.gethostname)
User Identity Integration
Identity Sources
- Current Windows / Active Directory user
(
ActiveDirectoryBasedUserIdentityin .NET, Java and C++) - Custom key-value user identities built from OS-level identifiers
- For web apps: the account identifier from your own authentication system
User Management
- Named user seats
- Seat reassignment, optionally with a minimum assignment length
- Enabling and disabling individual users and instances
- Linking user records (for example aliases of the same person)
- Session and usage history per user
Creating User Identities
Security Warning: User identity attributes must be obtained through repeatable, non-spoofable processes. Never use user-provided data (email addresses, phone numbers, etc.) as identity attributes, as these can be easily spoofed by anyone who knows the information. Use system-level identifiers that are automatically resolved, and keep them stable between launches (do not include process IDs, working directories or other values that change from run to run).
User Identity (.NET)
// Option 1: Current Windows user (recommended for Windows environments).
// Adds "UserName", plus "UPN" when the user is signed in to a domain.
var userIdentity = await ActiveDirectoryBasedUserIdentity.GetCurrentAsync();
// Option 2: Custom identity from OS-level identifiers
var sidIdentity = new UserIdentity(new Dictionary<string, string>
{
{ "sid", WindowsIdentity.GetCurrent().User.Value } // Windows SID - non-spoofable
});
// Values can also be added to an existing identity
var customUserIdentity = new UserIdentity();
customUserIdentity.Data.Add("uid", GetUnixUserId().ToString()); // e.g. via getuid() on Linux/macOS
// ❌ NEVER DO THIS - spoofable user input:
var insecureIdentity = new UserIdentity(new Dictionary<string, string>
{
{ "email", userEnteredEmail }, // ❌ User can enter any email
{ "employeeId", userEnteredId } // ❌ User can guess/know others' IDs
});
User Identity (Java)
import java.util.HashMap;
import java.util.Map;
// Option 1: Current OS / Active Directory user ("UserName", plus "UPN" on a domain)
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity.getCurrent();
// Option 2: Custom identity from a trusted directory (pseudo-code for LDAP)
LdapUser ldapUser = authenticateWithLdap();
Map<String, String> ldapIdentityData = new HashMap<>();
ldapIdentityData.put("distinguishedName", ldapUser.getDN()); // LDAP DN - non-spoofable
ldapIdentityData.put("objectGUID", ldapUser.getGUID()); // LDAP GUID - non-spoofable
UserIdentity enterpriseIdentity = new UserIdentity(ldapIdentityData);
// ❌ NEVER DO THIS - spoofable user input:
Map<String, String> insecureData = new HashMap<>();
insecureData.put("email", getUserInputEmail()); // ❌ User can enter any email
insecureData.put("badge", getUserInputBadgeNumber()); // ❌ User can know others' badges
User Identity (Python)
import os
import sys
from evoleap_licensing.UserIdentity import UserIdentity
if sys.platform == "win32":
# Requires pywin32; DOMAIN\user as resolved by Windows (not from environment variables)
import win32api
import win32con
user_identity = UserIdentity({
"UserName": win32api.GetUserNameEx(win32con.NameSamCompatible),
})
else:
import pwd
uid = os.getuid()
user_identity = UserIdentity({
"uid": str(uid), # Unix UID - non-spoofable
"username": pwd.getpwuid(uid).pw_name,
})
# ❌ NEVER DO THIS - spoofable user input:
insecure_identity_data = {
"email": input("Enter your email: "), # ❌ User can enter any email
"employee_id": input("Enter employee ID: "), # ❌ User can guess others' IDs
}
User Identity (JavaScript/Node.js)
// In the JavaScript SDK a user identity is a plain object: { version, data: Map }
// Web apps (typical): the account identifier from your own authentication system,
// which the end user cannot choose or change
const userIdentity = {
version: 1,
data: new Map([['accountId', String(session.accountId)]]),
};
// ❌ NEVER DO THIS - spoofable user input:
const insecureIdentity = {
version: 1,
data: new Map([
['email', req.body.email], // ❌ User can enter any email
['badge', req.body.badge], // ❌ User can know others' badges
]),
};
User Identity (C++)
#include "UserIdentity.h"
#include "ActiveDirectoryBasedUserIdentity.h"
#include <map>
#include <string>
// Option 1: Current Windows / Active Directory user (recommended)
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity::GetCurrent();
// Option 2: Custom identity from OS-level identifiers
std::map<std::string, std::string> data;
data["sid"] = GetCurrentUserSidString(); // e.g. via GetTokenInformation + ConvertSidToStringSidA
UserIdentity customUserIdentity(data);
// ❌ NEVER DO THIS - spoofable user input:
// std::map<std::string, std::string> insecureData;
// insecureData["email"] = userEnteredEmail; // ❌ Not secure
// insecureData["phone"] = userEnteredPhone; // ❌ Not secure
User Identity (Ruby)
# Server-side: identify the signed-in account from your own authentication system
user_identity = EvoleapLicensing::UserIdentity.new("uid" => current_user.id.to_s)
# ❌ NEVER DO THIS - spoofable user input:
insecure_identity = EvoleapLicensing::UserIdentity.new("email" => params[:email])
Best Practices for Identity Security
Secure Identity Attributes
- System Username: Operating system username, as resolved by the OS (not from environment variables)
- User ID (UID/SID): System-level user identifier (non-spoofable)
- Active Directory UPN / DN: From the signed-in domain account
- LDAP GUID: Unique identifier from LDAP directory
- Authenticated account ID: For web apps, the user's ID in your own authentication system
Insecure Identity Attributes (Never Use)
- Email Addresses: Can be spoofed by anyone who knows the email
- Phone Numbers: Can be entered by anyone who knows the number
- Employee IDs: Can be guessed or obtained by unauthorized users
- User-Entered Data: Any information provided directly by the user
- Volatile Values: Process IDs, working directories or runtime versions, which change between launches and break identity matching
Name and email are still collected at registration through UserInfo; they are
contact details, not identity.
Identity Patterns by Product Type
Desktop: User + Instance
Both user identity and hardware instance are tracked. Supports named-user, concurrent and node-locked licensing.
Server: Instance
The server instance is the only identity. Server products are validated per instance.
Web App: Instance + Users
The server instance is registered once; each user is registered against that instance and validated in their own session.
Web API: Identity-Agnostic
No instance or user identity - consumption is measured per API call against the license.
Sessions
elm's session management provides the runtime licensing enforcement layer, handling everything from initial validation to graceful session termination.
Session Lifecycle
1. Session Initialization
When your application starts, the SDK validates the current licensing state and establishes a session with the elm cloud platform.
// Session validation returns comprehensive state information
var sessionValidity = await controlManager.ValidateSessionAsync();
if (sessionValidity.IsValid) {
// Access session state, features and component entitlements
var sessionKey = controlManager.SessionState.SessionKey;
var availableFeatures = controlManager.ValidationState.Features;
}
2. Online vs Offline Operation
elm supports connected and offline scenarios. On a concurrent license (or a server product), a
seat can be checked out for offline use for a requested duration with
CheckOutLicenseForOfflineUseAsync; component tokens can be checked out with
CheckOutComponentTokensForOfflineUseAsync. For air-gapped sites, a whole unregistered
desktop or web app license can be checked out to an elm Lite server with
CheckOutFullLicenseAsync.
// Check session type and offline capabilities
if (sessionValidity.IsOfflineCheckout) {
Console.WriteLine($"Offline session, valid for: {sessionValidity.ValidityDuration}");
} else if (sessionValidity.IsInValidationFailureGracePeriod) {
Console.WriteLine($"elm unreachable - grace period ends {sessionValidity.GracePeriodExpiration}");
} else {
Console.WriteLine("Online session - periodically extended");
}
3. Session Heartbeat and Renewal
Active sessions are extended periodically (in .NET, SessionExtensionTimer can do this
for you). Each extension re-checks the license and returns updated features.
4. Graceful Session Termination
Proper session cleanup ensures accurate usage reporting and license availability for other users.
// Always end sessions when application closes
await controlManager.EndSessionAsync();
Session Types and Behaviors
Connected Sessions
Real-time validation with elm cloud services. Provides immediate license updates, usage tracking, and administrative control (for example remotely revoking a session).
Offline Checkout
Pre-authorized offline operation for environments with limited connectivity. The checkout lasts for the requested duration and counts as an active seat until it expires or is checked back in.
Grace Periods
If elm cannot be reached, a previously validated client keeps working for the license's
validation grace period (set per license, 7 days by default). Separately, the
ControlStrategy.GracePeriodForUnregisteredProduct client setting lets an
unregistered product run for a trial period.
Session State Management
elm maintains comprehensive session state information that your application can query for licensing decisions:
- License Validity: Whether the session is valid and, if not, the
InvalidReason(for exampleLicenseExpired,NoSeatsAvailable,SessionRevoked) - Feature Entitlements: Enabled features and feature groups
- License Properties Preview: The license's key-value custom data (API v3.5, not yet released)
- Component Access: Available components and current usage/restrictions
- Registration Data: Instance and user IDs and registration times
- Offline Data: Offline checkouts and sessions recorded while offline
Features and Components
elm provides two distinct mechanisms for controlling software functionality: Features for basic on/off functionality and Components for advanced usage-based licensing.
Features
- Simple named flags, enabled per license (and optionally per instance or user)
- Can be organized into feature groups
- Used for product editions (Basic, Pro, Enterprise)
- Inherit parent license restrictions
- No separate terms or pricing
- Examples: Advanced reporting, premium themes, API access
Components
- Independent licensing entities, optionally grouped into component groups
- Three types: session, checked-out token and consumable token
- Token requirements can vary by product version
- Component entitlements have their own terms, renewal periods (day, week, month, year) and overage allowances, or can be unlimited
- Support check-out/check-in workflows
- Not available for Server products
Component Types
Session Components
Restrict the number of simultaneous sessions using the component. Each component checkout consumes a session slot until checked back in. Ideal for resource-intensive operations.
Checked-Out Tokens
Components require tokens that are checked out during use and returned when done. Customers purchase token pools, allowing flexible resource allocation across teams and projects.
Consumable Tokens
Tokens are permanently consumed when used - suited to pay-per-use models like API calls, computation minutes, or processing credits. The entitlement can renew each period.
Implementation Patterns
Feature-Based Licensing (Simple On/Off)
Use features for basic functionality control where you need simple present/absent logic:
// Check if a feature is available
if (controlManager.ValidationState.Features.Contains("AdvancedReporting")) {
ShowAdvancedReportingUI();
} else {
ShowUpgradePrompt("Advanced Reporting requires Pro license");
}
Component-Based Licensing (Usage Control)
Use components when you need to control usage quantity or implement pay-per-use scenarios:
// Check out components for intensive operations (requires an active session)
var result = await controlManager.CheckOutComponentsAsync("RenderEngine");
if (result.Success) {
try {
PerformRenderingTask();
} finally {
await controlManager.CheckInComponentsAsync("RenderEngine");
}
}
Consumable Token Usage
For pay-per-use scenarios where consumption should be permanent:
// Consume tokens; omit the count to use the server-side requirement for this version
var consumeResult = await controlManager.CheckOutConsumableComponentAsync("APICredits", 1);
if (consumeResult.Success) {
ProcessAPIRequest();
// Token is permanently consumed - no check-in required
}
Licensing Workflow
Understanding the complete elm licensing workflow from initial deployment through ongoing operation and maintenance.
Phase 1: Product Setup and Configuration
1. Product Definition
Define your product in the elm portal with:
- Product Type: Desktop app, Server, Web app or Web API, based on deployment model
- Versions: Product versions, which carry the component token requirements
- Features: Define available features for different license tiers
- Components: Configure components and their types
- Public Key: Download the product's public key file; the SDKs use it to secure communication with elm
2. SDK Integration
Integrate the elm SDK into your application following the platform-specific integration guides above. Key integration points:
- Initialization: Configure the control manager with product ID, version and public key
- Registration: Handle first-time license key registration
- State Persistence: Store the control manager state securely between runs
- Session Management: Implement session validation at application startup
- Feature/Component Access: Guard functionality based on entitlements
- Error Handling: Gracefully handle licensing errors and edge cases
Phase 2: Customer Onboarding and License Distribution
3. License Generation
Create customer licenses through the elm portal or API:
- Customer: The company or business unit that owns the license
- License Configuration: License type, seat counts, restrictions, features and component entitlements
- Terms and Commercial Model: License validity periods and how the license is sold
- Distribution: Deliver the generated license key to the customer
4. Customer Registration Process
Guide customers through the initial registration workflow. The specific implementation depends on your product type:
Desktop Registration
User + instance identity with secure state storage using DPAPI or encrypted files.
Server Registration
Instance registration with state stored on the server.
Web App Registration
Server instance registration, then per-user registration against that instance.
Phase 3: Runtime Operation and Enforcement
5. Session-Based Enforcement
Every application session validates licensing and enforces entitlements. Implementation varies by product type:
Desktop Sessions
In-memory session state with secure file storage for validation state.
Server Validation
Periodic instance validation for the server.
Web App Sessions
Per-user session state in cookies or backend storage, with concurrent user management.
6. Usage Tracking and Reporting
elm records licensing activity for reporting:
- Sessions: Session start, extension and end per user/instance
- Registrations and Validations: Every registration and validation attempt and its result
- Component Consumption: Token usage and session-based component access
- Concurrent Usage: Active and peak usage against seat counts
- Change History: Records of modifications to licenses
Phase 4: Ongoing Management and Maintenance
7. License Lifecycle Management
Handle common license management scenarios:
- License Renewal: Add terms to extend a license
- User Management: Enable/disable users, reassign named-user seats
- Instance Management: Handle hardware changes and machine replacement
- Upgrades/Downgrades: Modify entitlements and feature access
8. Integration with Business Systems
Automate license management from your own systems through elm's APIs:
- CRM Integration: Sync customer data and license information
- Billing Systems: Drive billing from entitlements and usage
- Support Tools: Provide support teams with licensing visibility
- Analytics: Usage analytics for product planning and customer success
Decision Tree: Choosing the Right Licensing Model
Desktop Application
Choose if: Installable software, user-specific licensing needed
License types: Named user, concurrent or node-locked
Best for: CAD, development tools, productivity software
Server / Web App
Choose if: Deployed on a server; Web app when individual users need
licensing
Licensing: Server products per instance; Web app users by named-user or
concurrent licenses
Best for: Enterprise web apps, internal tools
Web API Service
Choose if: Cloud service, usage-based billing
Components: Consumable tokens only
Best for: SaaS APIs, consumption-based services