DOCS LLMs

C++ SDK

C++ SDK

Official C++ SDK for LicenseSeat. Add license validation to native applications, Unreal Engine games, and VST/AU audio plugins.

Building a VST plugin or Unreal game? We provide a single-header JUCE integration and an Unreal Engine plugin with zero external dependencies.

Features

  • License activation & deactivation - Automatic device fingerprinting
  • Online & offline validation - Ed25519 cryptographic verification
  • Entitlement checking - has_entitlement() and check_entitlement()
  • Local caching - File-based caching with clock tamper detection
  • Auto-validation - Background validation at configurable intervals
  • Event system - Subscribe to license events
  • Thread-safe - All public methods safe from any thread
  • Exception-free - Uses Result<T, Error> pattern

Installation

CMake (FetchContent)

include(FetchContent)

FetchContent_Declare(
    licenseseat
    GIT_REPOSITORY https://github.com/licenseseat/licenseseat-cpp.git
    GIT_TAG        main
)
FetchContent_MakeAvailable(licenseseat)

target_link_libraries(your_target PRIVATE licenseseat::licenseseat)

Manual Build

git clone https://github.com/licenseseat/licenseseat-cpp.git
cd licenseseat-cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install build

Dependencies

  • nlohmann/json - JSON parsing
  • cpp-httplib - HTTP client (with OpenSSL for HTTPS)

Cryptographic operations (Ed25519, SHA-256) use vendored libraries with no external dependencies.

Quick Start

#include <licenseseat/licenseseat.hpp>

int main()
{
    licenseseat::Config config;
    config.api_key = "pk_live_xxxxxxxx";
    config.product_slug = "your-product";

    licenseseat::Client client(config);

    // Validate a license
    auto result = client.validate("XXXX-XXXX-XXXX-XXXX");

    if (result.is_ok()) {
        const auto& validation = result.value();
        if (validation.valid) {
            std::cout << "License is valid!\n";
            std::cout << "Plan: " << validation.license.plan_key() << "\n";
        } else {
            std::cout << "Invalid: " << validation.code << " - " << validation.message << "\n";
        }
    } else {
        std::cerr << "Error: " << result.error_message() << "\n";
    }

    // Check entitlements
    if (client.has_entitlement("pro")) {
        // Enable pro features
    }

    return 0;
}

Configuration

licenseseat::Config config;

// Required
config.api_key = "pk_live_xxxxxxxx";
config.product_slug = "your-product";

// Optional - API settings
config.api_url = "https://licenseseat.com/api/v1";  // Default
config.timeout_seconds = 30;
config.max_retries = 3;

// Optional - Device identification
config.device_id = "";  // Auto-generated if empty

// Optional - Offline support
config.signing_public_key = "base64-ed25519-public-key";  // Pre-configure for offline
config.max_offline_days = 30;

// Optional - Caching
config.storage_path = "";  // Path for license cache (empty = no persistence)

// Optional - Auto-validation
config.auto_validate_interval = 3600.0;  // Seconds between background validations

Configuration Options

Option Type Default Description
api_key string required Your publishable API key
product_slug string required Product identifier
api_url string https://licenseseat.com/api/v1 API endpoint
timeout_seconds int 30 HTTP request timeout
max_retries int 3 Retry attempts for failed requests
device_id string "" Device identifier (auto-generated if empty)
signing_public_key string "" Ed25519 public key for offline verification
max_offline_days int 0 Maximum days offline (0 = disabled)
storage_path string "" Path for license cache (empty = no persistence)
auto_validate_interval double 3600.0 Seconds between auto-validation cycles

Validation

Validation checks if a license is valid. The API always returns HTTP 200 for validation - check the valid field to determine validity.

auto result = client.validate("LICENSE-KEY");

if (result.is_ok()) {
    const auto& validation = result.value();

    if (validation.valid) {
        // License is valid and usable
        std::cout << "Valid! Plan: " << validation.license.plan_key() << "\n";
    } else {
        // License exists but isn't valid for use
        // Common codes: expired, revoked, suspended, seat_limit_exceeded
        std::cout << "Code: " << validation.code << "\n";
        std::cout << "Message: " << validation.message << "\n";
    }

    // License data is always available (even when invalid)
    const auto& license = validation.license;
    std::cout << "Key: " << license.key() << "\n";
    std::cout << "Status: " << license_status_to_string(license.status()) << "\n";
    std::cout << "Seats: " << license.active_seats() << "/" << license.seat_limit() << "\n";
} else {
    // API error (license not found, network error, auth failed)
    std::cerr << "Error: " << result.error_message() << "\n";
}

Note: For hardware-locked licenses, you must provide a device_id to validate:

auto result = client.validate("LICENSE-KEY", device_id);

Without it, validation may return valid: false with code device_not_activated.

Async Validation

client.validate_async("LICENSE-KEY", [](licenseseat::Result<licenseseat::Validation> result) {
    if (result.is_ok() && result.value().valid) {
        // License is valid
    }
});

Activation

Activation binds a license to a device, consuming a seat.

auto result = client.activate("LICENSE-KEY", device_id, "My MacBook Pro");

if (result.is_ok()) {
    const auto& activation = result.value();
    std::cout << "Activation ID: " << activation.id() << "\n";
    std::cout << "Device: " << activation.device_name() << "\n";
} else {
    switch (result.error_code()) {
        case licenseseat::ErrorCode::SeatLimitExceeded:
            std::cerr << "No seats available\n";
            break;
        case licenseseat::ErrorCode::DeviceAlreadyActivated:
            std::cerr << "Device already activated\n";
            break;
        default:
            std::cerr << "Error: " << result.error_message() << "\n";
    }
}

Deactivation

Deactivation removes a device from a license, freeing a seat.

auto result = client.deactivate("LICENSE-KEY", device_id);

if (result.is_ok()) {
    std::cout << "Device deactivated\n";
} else if (result.error_code() == licenseseat::ErrorCode::ActivationNotFound) {
    std::cout << "Device was not activated\n";
}

Entitlements

// Simple boolean check (uses cached license data)
if (client.has_entitlement("pro")) {
    enable_pro_features();
}

// Detailed check with reason
auto entitlement = client.check_entitlement("feature-key");
if (entitlement.active) {
    // Feature unlocked
} else {
    std::cout << "Not available: " << entitlement.reason << "\n";
}

Status

Get the current cached license status without making a network request.

auto status = client.get_status();

std::cout << "Valid: " << (status.valid ? "yes" : "no") << "\n";
std::cout << "Code: " << status.code << "\n";

Offline Support

The SDK supports offline license validation using Ed25519 cryptographic signatures.

Offline Token Workflow

Step 1: Generate and cache offline token while online

// Generate offline token (requires network)
auto token_result = client.generate_offline_token("LICENSE-KEY");
if (token_result.is_error()) {
    std::cerr << "Failed to generate token: " << token_result.error_message() << "\n";
    return;
}
auto offline_token = token_result.value();

// Fetch signing key (requires network)
auto key_result = client.fetch_signing_key(offline_token.token.kid);
if (key_result.is_error()) {
    std::cerr << "Failed to fetch key: " << key_result.error_message() << "\n";
    return;
}
std::string public_key = key_result.value();

// Store both for offline use
save_to_disk(offline_token, public_key);

Step 2: Verify offline (no network required)

// Load cached data
auto [offline_token, public_key] = load_from_disk();

// Verify signature locally
auto verify_result = client.verify_offline_token(offline_token, public_key);
if (verify_result.is_ok() && verify_result.value()) {
    // Token is valid - license data available in offline_token.token
    std::cout << "License: " << offline_token.token.license_key << "\n";
    std::cout << "Plan: " << offline_token.token.plan_key << "\n";
    std::cout << "Expires: " << offline_token.token.exp << "\n";

    // Check entitlements from token
    for (const auto& ent : offline_token.token.entitlements) {
        std::cout << "Entitlement: " << ent.key << "\n";
    }
}

Pre-configured Public Key

For simpler deployments, pre-configure the signing public key:

licenseseat::Config config;
config.api_key = "pk_live_xxxxxxxx";
config.product_slug = "your-product";
config.signing_public_key = "MCowBQYDK2VwAyEA...";  // Your public key
config.max_offline_days = 30;

licenseseat::Client client(config);

// Now verify_offline_token can use the pre-configured key
auto result = client.verify_offline_token(offline_token);  // No key param needed

Offline Token Fields

Field Type Description
license_key string The license key
product_slug string Product identifier
plan_key string Plan identifier
mode string License mode (hardware_locked or floating)
seat_limit int Maximum allowed activations
device_id string Device this token is bound to (if hardware_locked)
iat int64 Issued at (Unix timestamp)
exp int64 Expires at (Unix timestamp)
nbf int64 Not valid before (Unix timestamp)
kid string Key ID for fetching the signing public key
entitlements array List of entitlements with keys and expiration
metadata object Custom metadata attached to the license

Auto-Validation

Background validation at configurable intervals.

// Configure interval (in seconds)
config.auto_validate_interval = 3600.0;  // Every hour

licenseseat::Client client(config);

// Start auto-validation
client.start_auto_validation("LICENSE-KEY");

// Check if running
if (client.is_auto_validating()) {
    std::cout << "Auto-validation is active\n";
}

// Stop when done
client.stop_auto_validation();

Events

Subscribe to license events for reactive updates.

#include <licenseseat/events.hpp>

// Subscribe to validation success
auto sub1 = client.on(licenseseat::events::VALIDATION_SUCCESS, [](const std::any& data) {
    std::cout << "License validated successfully!\n";
});

// Subscribe to validation failure
auto sub2 = client.on(licenseseat::events::VALIDATION_FAILED, [](const std::any& data) {
    std::cout << "License validation failed\n";
});

// Subscribe to offline token ready
auto sub3 = client.on(licenseseat::events::OFFLINE_TOKEN_READY, [](const std::any& data) {
    std::cout << "Offline token generated\n";
});

// Later: cancel subscriptions
sub1.cancel();
sub2.cancel();
sub3.cancel();

Available Events

Event Description
LICENSE_LOADED License data loaded from cache
ACTIVATION_START Activation request starting
ACTIVATION_SUCCESS Device activated successfully
ACTIVATION_ERROR Activation failed
VALIDATION_START Validation request starting
VALIDATION_SUCCESS License validated successfully
VALIDATION_FAILED License validation returned invalid
VALIDATION_ERROR Validation request failed (network, etc.)
VALIDATION_OFFLINE_SUCCESS Offline token verified successfully
VALIDATION_OFFLINE_FAILED Offline token verification failed
DEACTIVATION_START Deactivation request starting
DEACTIVATION_SUCCESS Device deactivated successfully
DEACTIVATION_ERROR Deactivation failed
NETWORK_ONLINE Network connectivity restored
NETWORK_OFFLINE Network connectivity lost
AUTOVALIDATION_CYCLE Auto-validation cycle completed
AUTOVALIDATION_STOPPED Auto-validation stopped
OFFLINE_TOKEN_READY Offline token generated
OFFLINE_TOKEN_VERIFIED Offline token verified
SDK_RESET SDK state reset

Error Handling

The SDK uses a Result<T, Error> pattern instead of exceptions.

auto result = client.validate("LICENSE-KEY");

if (result.is_ok()) {
    auto& validation = result.value();
    // Success - check validation.valid for license validity
} else {
    // Error - network, auth, or API error
    std::cerr << "Error: " << result.error_message() << "\n";

    switch (result.error_code()) {
        case licenseseat::ErrorCode::NetworkError:
            // No network connectivity
            break;
        case licenseseat::ErrorCode::LicenseNotFound:
            // Invalid license key
            break;
        case licenseseat::ErrorCode::AuthenticationFailed:
            // Invalid API key
            break;
        case licenseseat::ErrorCode::SeatLimitExceeded:
            // Too many activations
            break;
        // ... handle other cases
    }
}

Error Codes

Code Description
Success Operation completed successfully
NetworkError HTTP request failed (no connectivity)
ConnectionTimeout Request timed out
SSLError SSL/TLS error
InvalidLicenseKey License key format is invalid
LicenseNotFound License key not found in system
LicenseExpired License has expired
LicenseRevoked License has been revoked
LicenseSuspended License is suspended
LicenseNotActive License is not active
LicenseNotStarted License hasn't started yet
SeatLimitExceeded Maximum activations reached
ActivationNotFound Device activation not found
DeviceAlreadyActivated Device is already activated
ProductNotFound Product slug not found
AuthenticationFailed Invalid or missing API key
PermissionDenied API key lacks required permissions
ServerError Server-side error (5xx)
SigningNotConfigured Offline signing not configured
InvalidSignature Cryptographic signature invalid
FileError File read/write error

Unreal Engine Plugin

A complete UE plugin using native FHttpModule and FJsonObject. No external dependencies.

auto* LicenseSeat = GetGameInstance()->GetSubsystem<ULicenseSeatSubsystem>();

FLicenseSeatConfig Config;
Config.ApiKey = TEXT("pk_live_xxxxxxxx");
Config.ProductSlug = TEXT("your-game");
LicenseSeat->InitializeWithConfig(Config);

LicenseSeat->ValidateAsync(TEXT("LICENSE-KEY"),
    FOnValidationComplete::CreateLambda([](const FLicenseValidationResult& Result)
    {
        if (Result.bValid)
        {
            // License valid
        }
    }));

Location: integrations/unreal/LicenseSeat/

Features:

  • Blueprint support via UFUNCTION/UPROPERTY/USTRUCT
  • GameInstanceSubsystem for automatic lifecycle management
  • Async API (non-blocking)
  • Auto-validation timer
  • Ed25519 offline verification (ThirdParty folder pre-populated)

JUCE: VST / AU / AAX

A single-header integration using only JUCE's native HTTP (juce::URL) and JSON (juce::JSON), without any dependency on cpp-httplib, nlohmann/json, or OpenSSL.

#include "LicenseSeatJuceStandalone.h"

LicenseSeatJuceStandalone license("pk_live_xxxxxxxx", "your-plugin");

// Audio thread safe (reads std::atomic)
void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
{
    if (!license.isValid())
    {
        buffer.clear();
        return;
    }
    // Process audio
}

// Async validation (callback on message thread)
license.validateAsync("LICENSE-KEY", [](auto& result)
{
    if (result.valid)
    {
        // Update UI
    }
});

Location: integrations/juce/Source/LicenseSeatJuceStandalone.h

Features:

  • Single header file
  • std::atomic<bool> for lock-free status checks in audio thread
  • MessageManager::callAsync for thread-safe UI callbacks
  • Multi-instance safe (no global state)

Note: The standalone integration avoids OpenSSL symbol conflicts that occur when multiple plugins in the same DAW link different OpenSSL versions.

Thread Safety

All public methods are thread-safe. The SDK uses internal mutexes to protect shared state.

// Safe from multiple threads
std::thread t1([&client]() {
    client.validate("KEY");
});

std::thread t2([&client]() {
    bool valid = client.is_valid();
});

For audio plugins, isValid() uses std::atomic<bool> for lock-free reads in real-time contexts.

Platform Support

Platform Compiler Status
Linux GCC 9+, Clang 10+ Supported
macOS Apple Clang 12+ (ARM & Intel) Supported
Windows MSVC 2019+ Supported

Device Identification

The SDK automatically generates a unique device identifier:

  • macOS: IOKit Platform UUID
  • Windows: Machine GUID from registry
  • Linux: /etc/machine-id or hostname-based fallback

Reset

Clear all cached data (license, offline tokens, etc.).

client.reset();

Next Steps