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.

register_instance begin_user_session extend_session end_session once per install app starts each time the validity period runs out app exits
What the SDK does for a desktop app, using client API v3.4 calls. If elm can't be reached, the session stays valid for the license's grace period.

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-licensing vcpkg port, Windows x64): desktop (ControlManager) and server instance (ServerControlManager).
  • Ruby (evoleap-licensing gem): 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 (ActiveDirectoryBasedUserIdentity in .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 example LicenseExpired, 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