Industry
Use Case
Favorite Feature
A developer platform vendor distributes commercial SDKs and plugins—including analytics connectors, export adapters, and workflow automation extensions—alongside an open-source core platform. Plugin distribution previously lacked licensing controls: partners received SDK builds with no enforcement of which customers could deploy them, how many production instances were permitted, or whether usage was commercially licensed. The vendor needed a licensing solution that enforced entitlements at two distinct points in the SDK lifecycle—during development/build time and at runtime on customer deployments—while maintaining a smooth developer experience. NetLicensing’s multi-tier licensee model and node-locked licensing capabilities provided a programmatic distribution control mechanism that eliminated manual enforcement overhead.
SDK distribution in the vendor’s ecosystem faced several concrete problems:
This use case combines Node-Locked licensing (for per-deployment runtime enforcement) with Feature-based licensing (for SDK developer access).
Node-Locked Licensing binds a software license to a specific deployment environment. In this case, each production instance is identified by a unique deployment identifier (typically a machine hostname, container ID, or configuration hash). The NetLicensing API validates this identifier at plugin startup, ensuring that only licensed deployments can run the plugin.
Feature-Based Licensing controls which SDK variants developers can access. Each developer role (e.g., “SDK-Analytics-Developer”, “SDK-Export-Developer”) is modeled as a feature license. When a developer attempts to download an SDK, the developer portal validates that their account holds the corresponding feature license before serving the artifact.
In the NetLicensing Management Console:
COMM-SDK-SUITE (or similar)Commercial SDK Suite2.0PREDEFINED (partners will manage their own secret)Create three modules to organize SDK types and deployment tracking:
Module 1: SDK Developer Access
MOD-SDK-DEVSDK Developer AccessMulti-FeatureModule 2: Plugin Runtime Deployment
MOD-PLUGIN-RUNTIMEPlugin Runtime EntitlementsNode-LockedPREDEFINED (deployment identifier is provided by plugin at validation)Module 3: Partner Distribution Channel Control
MOD-CHANNEL-CONTROLDistribution Channel ManagementFeatureFor module MOD-SDK-DEV:
LT-ANALYTICS-DEVAnalytics SDK Developer LicenseFEATURE0 (included in suite)LT-EXPORT-DEVExport Adapter SDK Developer LicenseFEATURE0 (included in suite)LT-WORKFLOW-DEVWorkflow Automation SDK Developer LicenseFEATURE0 (included in suite)For module MOD-PLUGIN-RUNTIME:
LT-PLUGIN-DEPLOYPlugin Runtime Deployment LicenseFEATURE (enforced per node/deployment)USD 5,000/year (per production instance)For module MOD-CHANNEL-CONTROL:
LT-PARTNER-CHANNELPartner SDK Redistribution RightsFEATURE0When a developer attempts to download an SDK, the portal validates their entitlement:
# Developer clicks "Download Analytics SDK"
# Portal makes this API call:
POST https://go.netlicensing.io/core/v2/rest/licensee/{developerLicenseeNumber}/validate
Content-Type: application/x-www-form-urlencoded
productNumber=COMM-SDK-SUITE&productModuleNumber=MOD-SDK-DEV
Example Response (Valid):
{
"licensingModel": "MultiFeature",
"ttl": 3600,
"validations": {
"MOD-SDK-DEV": {
"valid": true,
"checkDate": "2026-04-19T10:30:00Z",
"licenseType": "FEATURE",
"licenses": [
{
"number": "L-ANALYTICS-DEV-001",
"type": "FEATURE",
"name": "Analytics SDK Developer License",
"active": true
}
]
}
}
}
Portal Logic:
def can_download_sdk(developer_id, sdk_type):
licensee_number = get_licensee_for_developer(developer_id)
response = validate_licensee(
licensee_number=licensee_number,
product_number="COMM-SDK-SUITE",
product_module_number="MOD-SDK-DEV"
)
if response['validations']['MOD-SDK-DEV']['valid']:
# Check for the specific SDK feature license
for license_obj in response['validations']['MOD-SDK-DEV']['licenses']:
if sdk_type == "ANALYTICS" and "Analytics" in license_obj['name']:
return True, "Download authorized"
return False, "SDK developer license not found"
When a deployed plugin starts, it validates its runtime entitlement:
# Plugin startup (customer production system)
# Plugin code generates deployment identifier:
DEPLOYMENT_ID = sha256(hostname + config_hash) # e.g., "prod-us-east-1-xyz123"
# Plugin makes this API call:
POST https://go.netlicensing.io/core/v2/rest/licensee/{customerLicenseeNumber}/validate
Content-Type: application/x-www-form-urlencoded
productNumber=COMM-SDK-SUITE&productModuleNumber=MOD-PLUGIN-RUNTIME&nodeSecret={DEPLOYMENT_ID}
Example Response (Valid):
{
"licensingModel": "NodeLocked",
"ttl": 86400,
"validations": {
"MOD-PLUGIN-RUNTIME": {
"valid": true,
"checkDate": "2026-04-19T14:15:00Z",
"licenseType": "FEATURE",
"licenses": [
{
"number": "L-PLUGIN-DEPLOY-PROD-001",
"type": "FEATURE",
"name": "Plugin Runtime Deployment License",
"active": true,
"nodeSecret": "prod-us-east-1-xyz123"
}
]
}
}
}
Plugin Startup Logic (Node.js example):
const crypto = require('crypto');
const https = require('https');
class PluginLicenseValidator {
constructor(customerLicenseeNumber) {
this.licenseeNumber = customerLicenseeNumber;
this.validationCache = null;
this.cacheExpiry = null;
}
getDeploymentId() {
const os = require('os');
const configHash = crypto.createHash('sha256')
.update(JSON.stringify(require('./config.json')))
.digest('hex');
return `${os.hostname()}-${configHash.substring(0, 8)}`;
}
async validateLicense() {
// Check cache first (valid for 24 hours)
if (this.validationCache && Date.now() < this.cacheExpiry) {
return this.validationCache;
}
const deploymentId = this.getDeploymentId();
const params = new URLSearchParams({
productNumber: 'COMM-SDK-SUITE',
productModuleNumber: 'MOD-PLUGIN-RUNTIME',
nodeSecret: deploymentId
});
return new Promise((resolve, reject) => {
const options = {
hostname: 'go.netlicensing.io',
path: `/core/v2/rest/licensee/${this.licenseeNumber}/validate?${params}`,
method: 'GET',
headers: {
'Authorization': `Basic ${Buffer.from('apiKey:secret').toString('base64')}`
}
};
https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
const result = JSON.parse(data);
// Cache for 24 hours
this.validationCache = result;
this.cacheExpiry = Date.now() + 86400000;
resolve(result);
});
}).on('error', reject).end();
});
}
async canStart() {
try {
const validation = await this.validateLicense();
const moduleValidation = validation.validations['MOD-PLUGIN-RUNTIME'];
return moduleValidation && moduleValidation.valid;
} catch (error) {
console.error('License validation failed:', error);
// Fall back to cached validation if available
return this.validationCache &&
this.validationCache.validations['MOD-PLUGIN-RUNTIME'].valid;
}
}
}
// Plugin startup
const validator = new PluginLicenseValidator('CUST-001');
if (await validator.canStart()) {
console.log('License valid - plugin starting');
// Plugin initialization
} else {
console.error('License invalid - plugin cannot start');
process.exit(1);
}
POST https://go.netlicensing.io/core/v2/rest/licensee
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {API_TOKEN}
productNumber=COMM-SDK-SUITE&number=DEV-JOHN-ACME&name=John+Developer+-+ACME+Corp&active=true&licenseeSecret=dev-secret-xyz123
Response:
{
"licensee": {
"number": "DEV-JOHN-ACME",
"active": true,
"productNumber": "COMM-SDK-SUITE",
"licenseeSecret": "dev-secret-xyz123",
"created": "2026-04-19T10:00:00Z"
}
}
POST https://go.netlicensing.io/core/v2/rest/licensee
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {API_TOKEN}
productNumber=COMM-SDK-SUITE&number=CUST-ACME-CORP&name=ACME+Corp+-+Analytics+Customer&active=true&licenseeSecret=cust-secret-abc456
POST https://go.netlicensing.io/core/v2/rest/license
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {API_TOKEN}
licenseeNumber=DEV-JOHN-ACME&licenseTemplateNumber=LT-ANALYTICS-DEV&name=Analytics+SDK+Access&active=true
POST https://go.netlicensing.io/core/v2/rest/license
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {API_TOKEN}
licenseeNumber=CUST-ACME-CORP&licenseTemplateNumber=LT-PLUGIN-DEPLOY&name=Production+Analytics+Deployment&active=true&nodeSecret=prod-us-east-1-xyz123
In this model, licensing is typically managed directly through the vendor’s sales team and contract negotiation rather than through the NetLicensing Shop. However, for partner self-service SDK license provisioning, a shop token can be generated:
POST https://go.netlicensing.io/core/v2/rest/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {API_TOKEN}
action=SHOP&licenseeNumber=PARTNER-XYZ&productNumber=COMM-SDK-SUITE&successUrl=https://partner.example.com/sdk/activated&cancelUrl=https://partner.example.com/sdk/cancel
Response:
{
"shopToken": {
"number": "SHOP-TOKEN-xyz789",
"successUrl": "https://partner.example.com/sdk/activated",
"cancelUrl": "https://partner.example.com/sdk/cancel"
}
}
The partner can then direct developers to:
https://go.netlicensing.io/shop/SHOP-TOKEN-xyz789
This provides a self-service licensing portal for SDK access requests.
Offline/Intermittent Connectivity:
Plugin deployments in restricted network environments may not reach NetLicensing immediately. Implement a grace period with cached validation:
async validateWithGracePeriod(deploymentId) {
try {
// Try to validate online
const result = await this.validateLicense();
// Cache result with extended TTL
localStorage.setItem('license_cache', JSON.stringify(result));
localStorage.setItem('cache_time', Date.now());
return result.validations['MOD-PLUGIN-RUNTIME'].valid;
} catch (networkError) {
// Fall back to cache if validation fails
const cached = localStorage.getItem('license_cache');
const cacheAge = Date.now() - parseInt(localStorage.getItem('cache_time'));
if (cached && cacheAge < 7 * 24 * 60 * 60 * 1000) { // 7-day grace
const result = JSON.parse(cached);
return result.validations['MOD-PLUGIN-RUNTIME'].valid;
}
throw new Error('License validation unavailable and cache expired');
}
}
License Transfer/Reactivation:
When a customer moves plugins to a new deployment environment:
# Update deployment identifier for existing license
PUT https://go.netlicensing.io/core/v2/rest/license/{licenseNumber}
Content-Type: application/x-www-form-urlencoded
nodeSecret=prod-us-west-2-new789
Audit Logging:
Log all SDK download and plugin validation attempts for compliance:
{
"event_type": "sdk_download_attempt",
"timestamp": "2026-04-19T10:30:00Z",
"developer_id": "DEV-JOHN-ACME",
"sdk_type": "ANALYTICS",
"result": "AUTHORIZED",
"deployment_id": null,
"ip_address": "203.0.113.42"
}
Error Handling & Retries:
async validateWithRetry(maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await this.validateLicense();
} catch (error) {
if (attempt === maxRetries) throw error;
// Exponential backoff: 1s, 2s, 4s
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt - 1) * 1000)
);
}
}
}
Rate Limiting:
NetLicensing imposes rate limits on the Validation API. For plugin deployments, implement request throttling:

Physical Security: Feature-Based Access Control with Time-Limited Permissions
A building management company implemented feature-based access control with time-limited permissions to manage resource gating and dynamic authorization across facilities.

AI/ML Platform: Token-Based Monetization with Inference Metering
Control API usage with Pay-per-Use or subscription models — charge per inference call, token consumed, or agent outcome