Licensor

Integration Examples

Common patterns and complete examples for integrating WiLicensor.

License Check Flow

+----------------+     +----------------+     +----------------+
|  Application   |     |   WiLicensor   |     |   Local Cache  |
|    Startup     |     |     Server     |     |                |
+-------+--------+     +-------+--------+     +-------+--------+
        |                      |                      |
        | 1. Check local cache |                      |
        |--------------------------------------------->|
        |                      |                      |
        |<---------------------------------------------|
        |         (cached license found)              |
        |                      |                      |
        | 2. Validate with server                     |
        |--------------------->|                      |
        |                      |                      |
        |<---------------------|                      |
        |    (validation result)                      |
        |                      |                      |
        | 3. Update cache      |                      |
        |--------------------------------------------->|
        |                      |                      |

Offline Grace Period

Handle temporary network failures gracefully:

C#

public class LicenseManager
{
    private const int GRACE_PERIOD_DAYS = 7;
    private readonly LicenseClient _client;
    private readonly string _cacheFile = "license_cache.json";

    public async Task<bool> CheckLicenseAsync(string email)
    {
        // Try online check first
        try
        {
            var result = await _client.ActivateLicenseAsync(email);
            if (result.IsValid)
            {
                SaveToCache(result);
                return true;
            }
        }
        catch (HttpRequestException)
        {
            // Network error - check cache
            var cached = LoadFromCache();
            if (cached != null)
            {
                var daysSinceValidation = (DateTime.UtcNow - cached.LastValidated).TotalDays;
                if (daysSinceValidation <= GRACE_PERIOD_DAYS)
                {
                    Console.WriteLine($"Offline mode: {GRACE_PERIOD_DAYS - (int)daysSinceValidation} days remaining");
                    return true;
                }
            }
        }

        return false;
    }

    private void SaveToCache(LicenseResponse response)
    {
        var cache = new CachedLicense
        {
            Key = response.Key,
            Condition = response.Condition,
            Type = response.Type,
            EndAt = response.EndAt,
            LastValidated = DateTime.UtcNow
        };
        File.WriteAllText(_cacheFile, JsonSerializer.Serialize(cache));
    }

    private CachedLicense LoadFromCache()
    {
        if (!File.Exists(_cacheFile)) return null;
        return JsonSerializer.Deserialize<CachedLicense>(File.ReadAllText(_cacheFile));
    }
}

public class CachedLicense
{
    public string Key { get; set; }
    public string Condition { get; set; }
    public string Type { get; set; }
    public DateTime? EndAt { get; set; }
    public DateTime LastValidated { get; set; }
}

Python

import json
import os
from datetime import datetime, timedelta

GRACE_PERIOD_DAYS = 7
CACHE_FILE = "license_cache.json"

def check_license_with_grace(client, email):
    """Check license with offline grace period."""

    # Try online check first
    try:
        result = client.activate_license(email)
        if result.is_valid:
            save_cache(result)
            return True
    except Exception as e:
        print(f"Network error: {e}")

        # Check cache for grace period
        cached = load_cache()
        if cached:
            last_validated = datetime.fromisoformat(cached["last_validated"])
            days_since = (datetime.utcnow() - last_validated).days

            if days_since <= GRACE_PERIOD_DAYS:
                remaining = GRACE_PERIOD_DAYS - days_since
                print(f"Offline mode: {remaining} days remaining")
                return True

    return False

def save_cache(result):
    cache = {
        "key": result.key,
        "condition": result.condition,
        "type": result.type,
        "end_at": result.end_at.isoformat() if result.end_at else None,
        "last_validated": datetime.utcnow().isoformat()
    }
    with open(CACHE_FILE, "w") as f:
        json.dump(cache, f)

def load_cache():
    if not os.path.exists(CACHE_FILE):
        return None
    with open(CACHE_FILE, "r") as f:
        return json.load(f)

Feature Flags via Settings

Use the settings field to enable/disable features:

Server Setup

In the WiLicensor admin panel, set the key's settings field:

"features": "basic,reports", "maxUsers": "5", "theme": "dark"

Client Usage

const result = await client.activateLicense(email);

if (result.isValid) {
  const settings = parseSettings(result.settings);

  // Parse features list
  const features = (settings.features || '').split(',').map(f => f.trim());

  // Check feature access
  const hasReports = features.includes('reports');
  const hasAdvanced = features.includes('advanced');

  // Get limits
  const maxUsers = parseInt(settings.maxUsers || '1', 10);

  console.log(`Features: ${features.join(', ')}`);
  console.log(`Max users: ${maxUsers}`);
}

Subscription Renewal Check

public async Task CheckSubscriptionAsync(string email)
{
    var result = await _client.ActivateLicenseAsync(email);

    if (result.Condition == "expired")
    {
        ShowRenewalDialog(result.EndAt);
        return;
    }

    if (result.Type == "subscription" && result.EndAt.HasValue)
    {
        var daysRemaining = (result.EndAt.Value - DateTime.UtcNow).TotalDays;

        if (daysRemaining <= 7)
        {
            ShowRenewalWarning((int)daysRemaining);
        }
    }
}

private void ShowRenewalWarning(int daysRemaining)
{
    MessageBox.Show(
        $"Your subscription expires in {daysRemaining} days. Please renew to continue using all features.",
        "Subscription Expiring",
        MessageBoxButtons.OK,
        MessageBoxIcon.Information
    );
}

Machine Transfer Flow

When a user moves to a new computer:

def transfer_to_new_machine(client, email, old_machine_file):
    """Transfer license from old machine to current machine."""

    # Load old machine ID from transferred file
    with open(old_machine_file, "r") as f:
        old_data = json.load(f)
        old_machine_id = old_data["machine_id"]

    # Transfer license
    result = client.transfer_license(email, old_machine_id)

    if result.is_valid:
        print("License transferred successfully!")

        # Save new machine info
        save_license({
            "key": result.key,
            "machine_id": result.machine_id,
            "email": email
        })

        return True
    else:
        if "already activated today" in (result.error or ""):
            print("Please wait 24 hours before transferring again.")
        else:
            print(f"Transfer failed: {result.error}")
        return False

Multi-Product Licensing

Handle multiple products with a single client:

class MultiProductLicenseManager {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.licenses = {};
  }

  async checkProduct(productName, email) {
    const client = new LicenseClient(this.baseUrl, productName);
    const result = await client.activateLicense(email);

    this.licenses[productName] = {
      isValid: result.isValid,
      type: result.type,
      settings: parseSettings(result.settings)
    };

    return result.isValid;
  }

  async checkAllProducts(products, email) {
    const results = {};

    for (const product of products) {
      results[product] = await this.checkProduct(product, email);
    }

    return results;
  }

  hasAccess(productName) {
    return this.licenses[productName]?.isValid || false;
  }

  getSettings(productName) {
    return this.licenses[productName]?.settings || {};
  }
}

// Usage
const manager = new MultiProductLicenseManager('https://license.yourserver.com');

const access = await manager.checkAllProducts(
  ['BasicModule', 'ProModule', 'EnterpriseModule'],
  'user@example.com'
);

console.log('Access:', access);
// { BasicModule: true, ProModule: true, EnterpriseModule: false }

if (manager.hasAccess('ProModule')) {
  enableProFeatures();
}

Webhook Integration (Server-Side)

If you're receiving webhooks from payment providers:

// Express webhook handler
app.post('/my-webhook/payment', async (req, res) => {
  const { email, productName, customerId } = req.body;

  // Create license via WiLicensor API (admin endpoint)
  try {
    const response = await fetch('https://license.yourserver.com/keys/add', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Cookie': 'admin-session-cookie' // Authenticated session
      },
      body: JSON.stringify({
        email: email,
        name: customerId,
        type: 'lifetime',
        productId: await getProductId(productName),
        condition: 'purchased'
      })
    });

    if (response.ok) {
      // Send license email to customer
      await sendLicenseEmail(email, productName);
    }

    res.json({ success: true });
  } catch (error) {
    console.error('License creation failed:', error);
    res.status(500).json({ error: 'Failed to create license' });
  }
});

Error Handling Best Practices

public async Task<LicenseStatus> ValidateLicenseAsync(string email)
{
    try
    {
        var result = await _client.ActivateLicenseAsync(email);

        // Handle specific conditions
        switch (result.Condition)
        {
            case "activated":
                return new LicenseStatus(true, "License is active");

            case "purchased":
                return new LicenseStatus(true, "License purchased, pending activation");

            case "expired":
                var expiredDate = result.EndAt?.ToString("d") ?? "unknown";
                return new LicenseStatus(false, $"License expired on {expiredDate}");

            case "deactivated":
                return new LicenseStatus(false, "License has been deactivated. Contact support.");

            default:
                return new LicenseStatus(false, $"Unknown status: {result.Condition}");
        }
    }
    catch (HttpRequestException ex)
    {
        // Network error
        return new LicenseStatus(false, "Unable to reach license server. Check your connection.");
    }
    catch (TaskCanceledException)
    {
        // Timeout
        return new LicenseStatus(false, "License server timeout. Please try again.");
    }
    catch (JsonException ex)
    {
        // Invalid response
        return new LicenseStatus(false, "Invalid response from license server.");
    }
    catch (Exception ex)
    {
        // Unexpected error
        Console.WriteLine($"License error: {ex}");
        return new LicenseStatus(false, "An unexpected error occurred.");
    }
}

public class LicenseStatus
{
    public bool IsValid { get; }
    public string Message { get; }

    public LicenseStatus(bool isValid, string message)
    {
        IsValid = isValid;
        Message = message;
    }
}

Back to Overview | C# Client | Python Client | JavaScript Client