Skip to main content

Licensing API Integration Guide

This guide explains how to integrate Pulse Billing's licensing API into any application using the license/validate endpoint. This API allows you to validate license keys, check subscription status, and manage feature access across your products.

Overview

The Pulse Billing Licensing API provides a secure way to:

  • Validate license tokens in real-time
  • Check subscription status and billing periods
  • Handle trial registrations and paid subscriptions
  • Manage product-specific license validation

License Validation Endpoint

Endpoint

POST https://dcm-be.secureteam.net/licensing/validate

Request Format

{
"licenseToken": "string",
"productCodes": ["string"],
"authContext": "string"
}

Parameters

ParameterTypeRequiredDescription
licenseTokenstringYesThe license token to validate
productCodesstring[]NoArray of product codes to validate against the license
authContextstringNoUnique identifier for tracking license usage (e.g., MAC address, machine ID)

Response Format

Success Response (200 OK)

{
"isTrial": false,
"contact": {
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]"
},
"productCode": "ACME_PRO",
"authStatus": "Success",
"companyName": "Acme Trading Corp"
}

Trial Response (200 OK)

{
"isTrial": true,
"contact": {
"firstName": "Jane",
"lastName": "Smith",
"email": "[email protected]"
},
"productCode": "NINJATRADER_BASIC",
"authStatus": "Success",
"companyName": "Trial Company"
}

Error Response (400 Bad Request)

{
"error": "license token field is required."
}

AuthStatus Values

StatusDescription
SuccessLicense is valid and active
FailedLicense validation failed
LockedOrder is locked out

Common Failure Reasons

  • "Invalid license token" - License token not found in database
  • "Invalid product code" - Product code doesn't match the license
  • "Trial expired" - Trial period has ended
  • "Order is locked" - Order has been locked out
  • "No payment placed" - No payment activity found for the order
  • "Subscription is due" - Subscription payment is overdue

Understanding authContext for License Protection

The authContext parameter is crucial for license misuse detection and protection. Pulse Billing tracks authentication events by authContext to identify when a license is being used across multiple machines or contexts simultaneously.

How License Misuse Detection Works

The system automatically monitors license usage patterns and blocks licenses when they're used across multiple machines:

  • Tracking: Every validation request with authContext is logged
  • Detection: Licenses are locked when used across multiple different authContext values
  • Action: Misused licenses automatically receive AuthStatus.Locked

Best Practices for authContext

Use Unique Machine Identifiers:

  • MAC address (recommended)
  • Machine ID or hardware fingerprint
  • User-specific device identifier
  • Session-based identifier (less secure)

Avoid These Patterns:

  • Static values like "default" or "machine1"
  • Values that change frequently (like IP addresses)
  • Shared identifiers across multiple machines

Machine-Specific License Binding Examples

// Get MAC address as authContext
const { exec } = require('child_process');
const { promisify } = require('util');

const execAsync = promisify(exec);

async function getMacAddress() {
try {
const { stdout } = await execAsync('getmac /v /fo csv');
const lines = stdout.split('\n');
if (lines.length > 1) {
const macLine = lines[1].split(',');
return macLine[0].replace(/"/g, '').trim();
}
} catch (error) {
console.error('Error getting MAC address:', error);
}
return null;
}

// Usage with MAC address
async function validateLicenseWithMac(licenseToken, productCodes) {
const macAddress = await getMacAddress();
const authContext = macAddress || 'fallback-machine-id';

const validator = new PulseLicenseValidator();
const result = await validator.validateLicense(licenseToken, productCodes, authContext);

if (result.valid) {
console.log(`License validated for machine: ${authContext}`);
return result;
} else {
console.error('License validation failed:', result.error);
return null;
}
}

// Alternative: Generate machine fingerprint
function generateMachineFingerprint() {
const os = require('os');
const crypto = require('crypto');

const machineInfo = {
hostname: os.hostname(),
platform: os.platform(),
arch: os.arch(),
cpus: os.cpus().length,
totalMemory: os.totalmem()
};

return crypto.createHash('sha256')
.update(JSON.stringify(machineInfo))
.digest('hex')
.substring(0, 16);
}

// Usage with machine fingerprint
const machineId = generateMachineFingerprint();
validator.validateLicense('LICENSE-TOKEN', ['ACME_PRO'], machineId);

License Misuse Scenarios

Scenario 1: Legitimate Usage

Machine A (MAC: AA:BB:CC:DD:EE:FF): 5 validations
Total: 5 validations across 1 context
Result: ✅ License remains active

Scenario 2: License Misuse Detected

Machine A (MAC: AA:BB:CC:DD:EE:FF): 6 validations
Machine B (MAC: 11:22:33:44:55:66): 4 validations
Machine C (MAC: 99:88:77:66:55:44): 2 validations
Total: 12 validations across 3 contexts
Result: ❌ License locked due to misuse

Scenario 3: Unlocked License

Previous misuse detected and license locked
Admin unlocks license via dashboard
All previous authContext tracking is cleared
Result: ✅ License active, fresh start

Integration Examples

class PulseLicenseValidator {
constructor(baseUrl = 'https://dcm-be.secureteam.net') {
this.baseUrl = baseUrl;
}

async validateLicense(licenseToken, productCodes = [], authContext = null) {
try {
const response = await fetch(`${this.baseUrl}/licensing/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
licenseToken: licenseToken,
productCodes: productCodes,
authContext: authContext
})
});

const result = await response.json();

if (response.ok) {
// API returns boolean for /validate endpoint
return {
success: true,
valid: result,
data: result
};
} else {
return {
success: false,
valid: false,
error: result.error || 'Validation failed'
};
}
} catch (error) {
return {
success: false,
valid: false,
error: 'Network error'
};
}
}

async getDetailedValidation(licenseToken, productCodes = [], authContext = null) {
try {
const response = await fetch(`${this.baseUrl}/licensing/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
licenseToken: licenseToken,
productCodes: productCodes,
authContext: authContext
})
});

const result = await response.json();

if (response.ok) {
return {
success: true,
data: result
};
} else {
return {
success: false,
error: result.error || 'Validation failed'
};
}
} catch (error) {
return {
success: false,
error: 'Network error'
};
}
}
}

// Usage example
const validator = new PulseLicenseValidator();

// Simple validation (returns boolean)
validator.validateLicense('LICENSE-TOKEN-HERE', ['ACME_PRO'], 'user-session-123')
.then(result => {
if (result.valid) {
console.log('License is valid');
enableFeatures();
} else {
console.error('License validation failed:', result.error);
handleInvalidLicense();
}
});

// Detailed validation (returns full response)
validator.getDetailedValidation('LICENSE-TOKEN-HERE', ['ACME_PRO'], 'user-session-123')
.then(result => {
if (result.success) {
const data = result.data;
console.log('License details:', data);

if (data.authStatus === 'Success') {
if (data.isTrial) {
console.log('Trial license for:', data.companyName);
} else {
console.log('Paid license for:', data.companyName);
}
enableFeatures();
} else {
console.log('License validation failed');
handleInvalidLicense();
}
} else {
console.error('Validation error:', result.error);
}
});

Feature Management

Implementing Feature Gates

class FeatureManager {
constructor(validationData) {
this.authStatus = validationData.authStatus;
this.isTrial = validationData.isTrial;
this.productCode = validationData.productCode;
this.companyName = validationData.companyName;
}

hasValidLicense() {
return this.authStatus === 'Success';
}

isTrialLicense() {
return this.isTrial === true;
}

canAccessFeature(featureName) {
if (!this.hasValidLicense()) {
return false;
}

// Define feature access based on product code and trial status
const featureAccess = {
'ACME_BASIC': {
'basic_indicators': true,
'standard_charts': true,
'premium_indicators': false,
'advanced_analytics': false
},
'ACME_PRO': {
'basic_indicators': true,
'standard_charts': true,
'premium_indicators': true,
'advanced_analytics': true
},
'ACME_ENTERPRISE': {
'basic_indicators': true,
'standard_charts': true,
'premium_indicators': true,
'advanced_analytics': true,
'custom_development': true,
'priority_support': true
}
};

const productFeatures = featureAccess[this.productCode] || {};
return productFeatures[featureName] === true;
}

getLicenseInfo() {
return {
valid: this.hasValidLicense(),
trial: this.isTrialLicense(),
product: this.productCode,
company: this.companyName
};
}
}

// Usage
const featureManager = new FeatureManager(validationData);

if (featureManager.hasValidLicense()) {
if (featureManager.isTrialLicense()) {
console.log('Using trial license');
showTrialLimitations();
}

if (featureManager.canAccessFeature('premium_indicators')) {
showPremiumIndicators();
}

if (featureManager.canAccessFeature('advanced_analytics')) {
showAdvancedAnalytics();
}
} else {
showLicenseExpiredMessage();
}

License Caching

class LicenseCache {
constructor() {
this.cache = new Map();
this.cacheTimeout = 24 * 60 * 60 * 1000; // 24 hours
}

getCachedLicense(licenseKey) {
const cached = this.cache.get(licenseKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
return null;
}

setCachedLicense(licenseKey, data) {
this.cache.set(licenseKey, {
data: data,
timestamp: Date.now()
});
}

invalidateLicense(licenseKey) {
this.cache.delete(licenseKey);
}
}

Error Handling

Common Error Scenarios

ScenarioDescriptionAuthStatusAction
Invalid License TokenLicense token not found in databaseFailedCheck license token format
Trial ExpiredTrial period has endedFailedPrompt for purchase
Order LockedOrder has been locked outLockedContact support
Invalid Product CodeProduct code doesn't match licenseFailedCheck product codes
No PaymentNo payment activity foundFailedComplete payment process
Subscription DueSubscription payment is overdueFailedPrompt for renewal
Network ErrorAPI request failedN/AUse cached data or enable limited mode