{"id":40597524,"url":"https://github.com/licenseseat/licenseseat-cpp","last_synced_at":"2026-02-09T07:02:11.919Z","repository":{"id":333589895,"uuid":"1137620661","full_name":"licenseseat/licenseseat-cpp","owner":"licenseseat","description":"Official C++ SDK for LicenseSeat - Easily add license keys to your game, plugin, or app ","archived":false,"fork":false,"pushed_at":"2026-01-21T04:53:07.000Z","size":193,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-21T13:43:56.047Z","etag":null,"topics":["cpp","cpp-library","license","license-checking","license-generator","license-management","licensing","licensing-as-a-service","licensing-server","unreal-engine","unreal-engine-4","unreal-engine-5","unreal-engine-plugin","vst","vst-plugin","vst-plugins"],"latest_commit_sha":null,"homepage":"https://licenseseat.com","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/licenseseat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-19T15:57:04.000Z","updated_at":"2026-01-21T04:53:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/licenseseat/licenseseat-cpp","commit_stats":null,"previous_names":["licenseseat/licenseseat-cpp"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/licenseseat/licenseseat-cpp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/licenseseat%2Flicenseseat-cpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/licenseseat%2Flicenseseat-cpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/licenseseat%2Flicenseseat-cpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/licenseseat%2Flicenseseat-cpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/licenseseat","download_url":"https://codeload.github.com/licenseseat/licenseseat-cpp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/licenseseat%2Flicenseseat-cpp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29258625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-09T04:11:57.159Z","status":"ssl_error","status_checked_at":"2026-02-09T04:11:56.117Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp-library","license","license-checking","license-generator","license-management","licensing","licensing-as-a-service","licensing-server","unreal-engine","unreal-engine-4","unreal-engine-5","unreal-engine-plugin","vst","vst-plugin","vst-plugins"],"created_at":"2026-01-21T04:05:42.270Z","updated_at":"2026-02-09T07:02:11.913Z","avatar_url":"https://github.com/licenseseat.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LicenseSeat C++ SDK\n\n[![CI](https://github.com/licenseseat/licenseseat-cpp/actions/workflows/ci.yml/badge.svg)](https://github.com/licenseseat/licenseseat-cpp/actions/workflows/ci.yml)\n[![C++17](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17)\n[![Platforms](https://img.shields.io/badge/Platforms-Windows%20|%20macOS%20|%20Linux-green.svg)](#platform-support)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\nThe official C++ SDK for [LicenseSeat](https://licenseseat.com) – the licensing platform for apps, games, and plugins.\n\n\u003e [!TIP]\n\u003e Building a **VST/AU plugin** or **Unreal Engine** game? We provide a [Unreal Engine plugin](#unreal-engine-plugin) and a [single-header integration for JUCE VST/AU plugins](#juce-vst--au--aax).\n\n---\n\n## Features\n\n- **License activation \u0026 deactivation** – Activate licenses with automatic device fingerprinting\n- **Online \u0026 offline validation** – Validate licenses with Ed25519 cryptographic verification\n- **Entitlement checking** – Check feature access with `has_entitlement()` and `check_entitlement()`\n- **Local caching** – File-based caching with clock tamper detection\n- **Auto-validation** – Background validation with configurable intervals\n- **Heartbeat** – Track active device usage with manual or automatic heartbeats\n- **Telemetry** – Anonymous platform telemetry (16 fields, opt-out with one flag)\n- **Event system** – Subscribe to license events (validation success/failure, heartbeat, offline token ready, etc.)\n- **Thread-safe** – All public methods are safe to call from multiple threads\n- **Cross-platform** – Windows, macOS, and Linux support\n- **Exception-free** – Uses `Result\u003cT, Error\u003e` pattern for error handling\n\n---\n\n## Special Packaging\n\nThe SDK includes zero-dependency integrations for platforms where dependency management is particularly challenging.\n\n### Unreal Engine Plugin\n\nA complete UE plugin using native `FHttpModule` and `FJsonObject`. No external dependencies.\n\n```cpp\nauto* LicenseSeat = GetGameInstance()-\u003eGetSubsystem\u003cULicenseSeatSubsystem\u003e();\n\nFLicenseSeatConfig Config;\nConfig.ApiKey = TEXT(\"your-api-key\");\nConfig.ProductSlug = TEXT(\"your-game\");\nLicenseSeat-\u003eInitializeWithConfig(Config);\n\nLicenseSeat-\u003eValidateAsync(TEXT(\"LICENSE-KEY\"),\n    FOnValidationComplete::CreateLambda([](const FLicenseValidationResult\u0026 Result)\n    {\n        if (Result.bValid)\n        {\n            // License valid\n        }\n    }));\n```\n\n**Location:** [`integrations/unreal/LicenseSeat/`](integrations/unreal/)\n\n- Blueprint support via `UFUNCTION`/`UPROPERTY`/`USTRUCT`\n- `GameInstanceSubsystem` for automatic lifecycle management\n- Async API (non-blocking)\n- Auto-validation timer\n- Ed25519 offline verification (ThirdParty folder pre-populated)\n\n---\n\n### JUCE: VST / AU / AAX\n\nA 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.\n\n```cpp\n#include \"LicenseSeatJuceStandalone.h\"\n\nLicenseSeatJuceStandalone license(\"your-api-key\", \"your-plugin\");\n\n// Audio thread safe (reads std::atomic)\nvoid processBlock(juce::AudioBuffer\u003cfloat\u003e\u0026 buffer, juce::MidiBuffer\u0026)\n{\n    if (!license.isValid())\n    {\n        buffer.clear();\n        return;\n    }\n    // Process audio\n}\n\n// Async validation (callback on message thread)\nlicense.validateAsync(\"LICENSE-KEY\", [](auto\u0026 result)\n{\n    if (result.valid)\n    {\n        // Update UI\n    }\n});\n```\n\n**Location:** [`integrations/juce/Source/LicenseSeatJuceStandalone.h`](integrations/juce/)\n\n- Single header file\n- `std::atomic\u003cbool\u003e` for lock-free status checks in audio thread\n- `MessageManager::callAsync` for thread-safe UI callbacks\n- Multi-instance safe (no global state)\n\n\u003e [!NOTE]\n\u003e The standalone integration avoids OpenSSL symbol conflicts that occur when multiple plugins in the same DAW link different OpenSSL versions.\n\n---\n\n## Installation\n\n### CMake (FetchContent)\n\n```cmake\ninclude(FetchContent)\n\nFetchContent_Declare(\n    licenseseat\n    GIT_REPOSITORY https://github.com/licenseseat/licenseseat-cpp.git\n    GIT_TAG        main\n)\nFetchContent_MakeAvailable(licenseseat)\n\ntarget_link_libraries(your_target PRIVATE licenseseat::licenseseat)\n```\n\n### Manual Build\n\n```bash\ngit clone https://github.com/licenseseat/licenseseat-cpp.git\ncd licenseseat-cpp\ncmake -B build -DCMAKE_BUILD_TYPE=Release\ncmake --build build\nsudo cmake --install build\n```\n\n### Dependencies\n\n- **nlohmann/json** – JSON parsing\n- **cpp-httplib** – HTTP client (with OpenSSL for HTTPS)\n\nCryptographic operations (Ed25519, SHA-256) use vendored libraries with no external dependencies.\n\n---\n\n## Quick Start\n\n```cpp\n#include \u003clicenseseat/licenseseat.hpp\u003e\n\nint main()\n{\n    licenseseat::Config config;\n    config.api_key = \"your-api-key\";\n    config.product_slug = \"your-product\";\n\n    licenseseat::Client client(config);\n\n    // Validate a license\n    auto result = client.validate(\"XXXX-XXXX-XXXX-XXXX\");\n\n    if (result.is_ok()) {\n        const auto\u0026 validation = result.value();\n        if (validation.valid) {\n            std::cout \u003c\u003c \"License is valid!\\n\";\n            std::cout \u003c\u003c \"Plan: \" \u003c\u003c validation.license.plan_key() \u003c\u003c \"\\n\";\n        } else {\n            // License exists but validation failed (e.g., expired, seat limit)\n            std::cout \u003c\u003c \"Invalid: \" \u003c\u003c validation.code \u003c\u003c \" - \" \u003c\u003c validation.message \u003c\u003c \"\\n\";\n        }\n    } else {\n        // Network error, license not found, etc.\n        std::cerr \u003c\u003c \"Error: \" \u003c\u003c result.error_message() \u003c\u003c \"\\n\";\n    }\n\n    // Check entitlements\n    if (client.has_entitlement(\"pro\")) {\n        // Enable pro features\n    }\n\n    return 0;\n}\n```\n\n---\n\n## Configuration\n\n```cpp\nlicenseseat::Config config;\n\n// Required\nconfig.api_key = \"your-api-key\";\nconfig.product_slug = \"your-product\";\n\n// Optional - API settings\nconfig.api_url = \"https://licenseseat.com/api/v1\";  // Default\nconfig.timeout_seconds = 30;\nconfig.max_retries = 3;\n\n// Optional - Device identification\nconfig.device_id = \"\";  // Auto-generated if empty\n\n// Optional - Offline support\nconfig.signing_public_key = \"base64-ed25519-public-key\";  // Pre-configure for offline\nconfig.max_offline_days = 30;\n\n// Optional - Caching\nconfig.storage_path = \"\";  // Path for license cache (empty = no persistence)\n\n// Optional - Auto-validation\nconfig.auto_validate_interval = 3600.0;  // Seconds between background validations\n\n// Optional - Heartbeat\nconfig.heartbeat_interval = 300;  // Seconds between auto-heartbeats (0 to disable)\n\n// Optional - Telemetry\nconfig.telemetry_enabled = true;   // Set false to disable\nconfig.app_version = \"2.1.0\";     // Included in telemetry\nconfig.app_build = \"142\";         // Included in telemetry\n```\n\n### Configuration Options\n\n| Option                   | Type   | Default                            | Description                                        |\n| ------------------------ | ------ | ---------------------------------- | -------------------------------------------------- |\n| `api_key`                | string | *required*                         | API key for authentication                         |\n| `product_slug`           | string | *required*                         | Product identifier                                 |\n| `api_url`                | string | `https://licenseseat.com/api/v1`   | API endpoint                                       |\n| `timeout_seconds`        | int    | `30`                               | HTTP request timeout                               |\n| `max_retries`            | int    | `3`                                | Retry attempts for failed requests                 |\n| `device_id`              | string | `\"\"`                               | Device identifier (auto-generated if empty)        |\n| `signing_public_key`     | string | `\"\"`                               | Ed25519 public key for offline verification        |\n| `max_offline_days`       | int    | `0`                                | Maximum days license works offline (0 = disabled)  |\n| `storage_path`           | string | `\"\"`                               | Path for license cache (empty = no persistence)    |\n| `auto_validate_interval` | double | `3600.0`                           | Seconds between auto-validation cycles             |\n| `telemetry_enabled`      | bool   | `true`                             | Enable anonymous telemetry collection              |\n| `app_version`            | string | `\"\"`                               | App version included in telemetry                  |\n| `app_build`              | string | `\"\"`                               | Build number included in telemetry                 |\n| `heartbeat_interval`     | int    | `300`                              | Seconds between auto-heartbeats (0 to disable)     |\n\n---\n\n## Telemetry \u0026 Privacy\n\nThe SDK collects anonymous platform telemetry to help developers understand their user base. This data is sent alongside every API request (validation, activation, heartbeat, etc.) and includes the following fields:\n\n| Field               | Type   | Example                  | Description                                            |\n| ------------------- | ------ | ------------------------ | ------------------------------------------------------ |\n| `sdk_name`          | string | `\"cpp\"`                  | Always `\"cpp\"` for this SDK                            |\n| `sdk_version`       | string | `\"0.4.0\"`               | SDK version                                            |\n| `os_name`           | string | `\"macOS\"`                | Operating system (`\"macOS\"`, `\"Windows\"`, `\"Linux\"`)   |\n| `os_version`        | string | `\"15.3\"`                 | OS version string                                      |\n| `platform`          | string | `\"native\"`               | Always `\"native\"` for this SDK                         |\n| `device_model`      | string | `\"Mac14,2\"`              | Hardware model identifier                              |\n| `device_type`       | string | `\"desktop\"`              | Device type (`\"desktop\"`, `\"server\"`, `\"unknown\"`)     |\n| `architecture`      | string | `\"arm64\"`                | CPU architecture (`\"arm64\"`, `\"x64\"`, `\"x86\"`, `\"arm\"`) |\n| `cpu_cores`         | int    | `10`                     | Number of logical CPU cores                            |\n| `memory_gb`         | int    | `16`                     | Total system RAM in GB (rounded)                       |\n| `locale`            | string | `\"en_US.UTF-8\"`          | System locale string                                   |\n| `language`           | string | `\"en\"`                   | Two-letter language code extracted from locale          |\n| `timezone`          | string | `\"America/New_York\"`     | System timezone                                        |\n| `screen_resolution` | string | `\"1920x1080\"`            | Primary display resolution                             |\n| `app_version`       | string | `\"2.1.0\"`               | User-provided via `config.app_version`                 |\n| `app_build`         | string | `\"142\"`                  | User-provided via `config.app_build`                   |\n\nNo personally identifiable information is collected. Fields that cannot be determined on a given platform are omitted from the payload.\n\nTo include your application version in telemetry:\n\n```cpp\nconfig.app_version = \"2.1.0\";\nconfig.app_build = \"142\";\n```\n\nTo disable telemetry entirely:\n\n```cpp\nconfig.telemetry_enabled = false;\n```\n\n---\n\n## Heartbeat\n\nHeartbeats let the server know a device is actively using a license. This powers the \"last seen\" timestamp and active-device analytics in the LicenseSeat dashboard.\n\n### Manual Heartbeat\n\nSend a one-off heartbeat at any time:\n\n```cpp\nauto result = client.heartbeat(\"LICENSE-KEY\");\n\nif (result.is_ok()) {\n    std::cout \u003c\u003c \"Heartbeat received at: \" \u003c\u003c result.value().received_at \u003c\u003c \"\\n\";\n}\n\n// Async variant\nclient.heartbeat_async(\"LICENSE-KEY\", [](auto result) {\n    if (result.is_ok()) {\n        std::cout \u003c\u003c \"Heartbeat sent\\n\";\n    }\n});\n```\n\n### Auto-Heartbeat Timer\n\nStart a background timer that sends heartbeats at a regular interval:\n\n```cpp\n// Configure interval (default is 300 seconds / 5 minutes)\nconfig.heartbeat_interval = 600;  // Every 10 minutes (0 to disable)\n\nlicenseseat::Client client(config);\n\n// Start the timer\nclient.start_heartbeat(\"LICENSE-KEY\");\n\n// Check if running\nif (client.is_heartbeat_running()) {\n    std::cout \u003c\u003c \"Heartbeat timer is active\\n\";\n}\n\n// Stop when done (also stops automatically on destruction)\nclient.stop_heartbeat();\n```\n\nHeartbeats are also sent automatically as part of each auto-validation cycle, so if you are already using `start_auto_validation()` you may not need a separate heartbeat timer.\n\n---\n\n## API Reference\n\n### Validation\n\nValidation checks if a license is valid. **Important:** The API always returns HTTP 200 for validation – check the `valid` field to determine validity.\n\n```cpp\nauto result = client.validate(\"LICENSE-KEY\");\n\nif (result.is_ok()) {\n    const auto\u0026 validation = result.value();\n\n    if (validation.valid) {\n        // License is valid and usable\n        std::cout \u003c\u003c \"Valid! Plan: \" \u003c\u003c validation.license.plan_key() \u003c\u003c \"\\n\";\n    } else {\n        // License exists but isn't valid for use\n        // Common codes: expired, revoked, suspended, seat_limit_exceeded\n        std::cout \u003c\u003c \"Code: \" \u003c\u003c validation.code \u003c\u003c \"\\n\";\n        std::cout \u003c\u003c \"Message: \" \u003c\u003c validation.message \u003c\u003c \"\\n\";\n    }\n\n    // License data is always available (even when invalid)\n    const auto\u0026 license = validation.license;\n    std::cout \u003c\u003c \"Key: \" \u003c\u003c license.key() \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"Status: \" \u003c\u003c license_status_to_string(license.status()) \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"Seats: \" \u003c\u003c license.active_seats() \u003c\u003c \"/\" \u003c\u003c license.seat_limit() \u003c\u003c \"\\n\";\n} else {\n    // API error (license not found, network error, auth failed)\n    std::cerr \u003c\u003c \"Error: \" \u003c\u003c result.error_message() \u003c\u003c \"\\n\";\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e For **hardware-locked** licenses, you must provide a `device_id` to validate:\n\u003e ```cpp\n\u003e auto result = client.validate(\"LICENSE-KEY\", device_id);\n\u003e ```\n\u003e Without it, validation may return `valid: false` with code `device_not_activated`.\n\n### Async Validation\n\n```cpp\nclient.validate_async(\"LICENSE-KEY\", [](licenseseat::Result\u003clicenseseat::Validation\u003e result) {\n    if (result.is_ok() \u0026\u0026 result.value().valid) {\n        // License is valid\n    }\n});\n```\n\n### Activation\n\nActivation binds a license to a device, consuming a seat.\n\n```cpp\nauto result = client.activate(\"LICENSE-KEY\", device_id, \"My MacBook Pro\");\n\nif (result.is_ok()) {\n    const auto\u0026 activation = result.value();\n    std::cout \u003c\u003c \"Activation ID: \" \u003c\u003c activation.id() \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"Device: \" \u003c\u003c activation.device_name() \u003c\u003c \"\\n\";\n} else {\n    switch (result.error_code()) {\n        case licenseseat::ErrorCode::SeatLimitExceeded:\n            std::cerr \u003c\u003c \"No seats available\\n\";\n            break;\n        case licenseseat::ErrorCode::DeviceAlreadyActivated:\n            std::cerr \u003c\u003c \"Device already activated\\n\";\n            break;\n        default:\n            std::cerr \u003c\u003c \"Error: \" \u003c\u003c result.error_message() \u003c\u003c \"\\n\";\n    }\n}\n```\n\n### Deactivation\n\nDeactivation removes a device from a license, freeing a seat.\n\n```cpp\nauto result = client.deactivate(\"LICENSE-KEY\", device_id);\n\nif (result.is_ok()) {\n    std::cout \u003c\u003c \"Device deactivated\\n\";\n} else if (result.error_code() == licenseseat::ErrorCode::ActivationNotFound) {\n    std::cout \u003c\u003c \"Device was not activated\\n\";\n}\n```\n\n### Entitlement Checks\n\n```cpp\n// Simple boolean check (uses cached license data)\nif (client.has_entitlement(\"pro\")) {\n    enable_pro_features();\n}\n\n// Detailed check with reason\nauto entitlement = client.check_entitlement(\"feature-key\");\nif (entitlement.active) {\n    // Feature unlocked\n} else {\n    std::cout \u003c\u003c \"Not available: \" \u003c\u003c entitlement.reason \u003c\u003c \"\\n\";\n}\n```\n\n### Status\n\nGet the current cached license status without making a network request.\n\n```cpp\nauto status = client.get_status();\n\nstd::cout \u003c\u003c \"Valid: \" \u003c\u003c (status.valid ? \"yes\" : \"no\") \u003c\u003c \"\\n\";\nstd::cout \u003c\u003c \"Code: \" \u003c\u003c status.code \u003c\u003c \"\\n\";\n```\n\n### Reset\n\nClear all cached data (license, offline tokens, etc.).\n\n```cpp\nclient.reset();\n```\n\n---\n\n## Offline Support\n\nThe SDK supports offline license validation using Ed25519 cryptographic signatures. This allows your application to work without network access after initial setup.\n\n### Offline Token Workflow\n\n**Step 1: Generate and cache offline token while online**\n\n```cpp\n// Generate offline token (requires network)\nauto token_result = client.generate_offline_token(\"LICENSE-KEY\");\nif (token_result.is_error()) {\n    std::cerr \u003c\u003c \"Failed to generate token: \" \u003c\u003c token_result.error_message() \u003c\u003c \"\\n\";\n    return;\n}\nauto offline_token = token_result.value();\n\n// Fetch signing key (requires network)\nauto key_result = client.fetch_signing_key(offline_token.token.kid);\nif (key_result.is_error()) {\n    std::cerr \u003c\u003c \"Failed to fetch key: \" \u003c\u003c key_result.error_message() \u003c\u003c \"\\n\";\n    return;\n}\nstd::string public_key = key_result.value();\n\n// Store both for offline use\nsave_to_disk(offline_token, public_key);\n```\n\n**Step 2: Verify offline (no network required)**\n\n```cpp\n// Load cached data\nauto [offline_token, public_key] = load_from_disk();\n\n// Verify signature locally\nauto verify_result = client.verify_offline_token(offline_token, public_key);\nif (verify_result.is_ok() \u0026\u0026 verify_result.value()) {\n    // Token is valid - license data available in offline_token.token\n    std::cout \u003c\u003c \"License: \" \u003c\u003c offline_token.token.license_key \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"Plan: \" \u003c\u003c offline_token.token.plan_key \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"Expires: \" \u003c\u003c offline_token.token.exp \u003c\u003c \"\\n\";\n\n    // Check entitlements from token\n    for (const auto\u0026 ent : offline_token.token.entitlements) {\n        std::cout \u003c\u003c \"Entitlement: \" \u003c\u003c ent.key \u003c\u003c \"\\n\";\n    }\n}\n```\n\n### Pre-configured Public Key\n\nFor simpler deployments, you can pre-configure the signing public key:\n\n```cpp\nlicenseseat::Config config;\nconfig.api_key = \"your-api-key\";\nconfig.product_slug = \"your-product\";\nconfig.signing_public_key = \"MCowBQYDK2VwAyEA...\";  // Your public key\nconfig.max_offline_days = 30;\n\nlicenseseat::Client client(config);\n\n// Now verify_offline_token can use the pre-configured key\nauto result = client.verify_offline_token(offline_token);  // No key param needed\n```\n\n### Offline Token Fields\n\n| Field              | Type     | Description                                      |\n| ------------------ | -------- | ------------------------------------------------ |\n| `license_key`      | string   | The license key                                  |\n| `product_slug`     | string   | Product identifier                               |\n| `plan_key`         | string   | Plan identifier (e.g., \"pro-annual\")             |\n| `mode`             | string   | License mode (\"hardware_locked\" or \"floating\")   |\n| `seat_limit`       | int      | Maximum allowed activations                      |\n| `device_id`        | string   | Device this token is bound to (if hardware_locked) |\n| `iat`              | int64    | Issued at (Unix timestamp)                       |\n| `exp`              | int64    | Expires at (Unix timestamp)                      |\n| `nbf`              | int64    | Not valid before (Unix timestamp)                |\n| `license_expires_at` | int64  | License expiration (Unix timestamp, may be 0)    |\n| `kid`              | string   | Key ID for fetching the signing public key       |\n| `entitlements`     | array    | List of entitlements with keys and expiration    |\n| `metadata`         | object   | Custom metadata attached to the license          |\n\n---\n\n## Auto-Validation\n\nThe SDK can automatically validate licenses in the background at configurable intervals.\n\n```cpp\n// Configure interval (in seconds)\nconfig.auto_validate_interval = 3600.0;  // Every hour\n\nlicenseseat::Client client(config);\n\n// Start auto-validation\nclient.start_auto_validation(\"LICENSE-KEY\");\n\n// Check if running\nif (client.is_auto_validating()) {\n    std::cout \u003c\u003c \"Auto-validation is active\\n\";\n}\n\n// Stop when done\nclient.stop_auto_validation();\n```\n\n---\n\n## Events\n\nSubscribe to license events for reactive updates.\n\n```cpp\n#include \u003clicenseseat/events.hpp\u003e\n\n// Subscribe to validation success\nauto sub1 = client.on(licenseseat::events::VALIDATION_SUCCESS, [](const std::any\u0026 data) {\n    std::cout \u003c\u003c \"License validated successfully!\\n\";\n});\n\n// Subscribe to validation failure\nauto sub2 = client.on(licenseseat::events::VALIDATION_FAILED, [](const std::any\u0026 data) {\n    std::cout \u003c\u003c \"License validation failed\\n\";\n});\n\n// Subscribe to offline token ready\nauto sub3 = client.on(licenseseat::events::OFFLINE_TOKEN_READY, [](const std::any\u0026 data) {\n    std::cout \u003c\u003c \"Offline token generated\\n\";\n});\n\n// Later: cancel subscriptions\nsub1.cancel();\nsub2.cancel();\nsub3.cancel();\n```\n\n### Available Events\n\n| Event Name               | Description                                  |\n| ------------------------ | -------------------------------------------- |\n| `LICENSE_LOADED`         | License data loaded from cache               |\n| `ACTIVATION_START`       | Activation request starting                  |\n| `ACTIVATION_SUCCESS`     | Device activated successfully                |\n| `ACTIVATION_ERROR`       | Activation failed                            |\n| `VALIDATION_START`       | Validation request starting                  |\n| `VALIDATION_SUCCESS`     | License validated successfully               |\n| `VALIDATION_FAILED`      | License validation returned invalid          |\n| `VALIDATION_ERROR`       | Validation request failed (network, etc.)    |\n| `VALIDATION_OFFLINE_SUCCESS` | Offline token verified successfully      |\n| `VALIDATION_OFFLINE_FAILED`  | Offline token verification failed        |\n| `DEACTIVATION_START`     | Deactivation request starting                |\n| `DEACTIVATION_SUCCESS`   | Device deactivated successfully              |\n| `DEACTIVATION_ERROR`     | Deactivation failed                          |\n| `NETWORK_ONLINE`         | Network connectivity restored                |\n| `NETWORK_OFFLINE`        | Network connectivity lost                    |\n| `AUTOVALIDATION_CYCLE`   | Auto-validation cycle completed              |\n| `AUTOVALIDATION_STOPPED` | Auto-validation stopped                      |\n| `OFFLINE_TOKEN_READY`    | Offline token generated                      |\n| `OFFLINE_TOKEN_VERIFIED` | Offline token verified                       |\n| `HEARTBEAT_SUCCESS`      | Heartbeat sent successfully                  |\n| `HEARTBEAT_ERROR`        | Heartbeat request failed                     |\n| `SDK_RESET`              | SDK state reset                              |\n\n---\n\n## Thread Safety\n\nAll public methods are thread-safe. The SDK uses internal mutexes to protect shared state.\n\n```cpp\n// Safe from multiple threads\nstd::thread t1([\u0026client]() {\n    client.validate(\"KEY\");\n});\n\nstd::thread t2([\u0026client]() {\n    bool valid = client.is_valid();\n});\n```\n\nFor audio plugins, `isValid()` uses `std::atomic\u003cbool\u003e` for lock-free reads in real-time contexts.\n\n---\n\n## Error Handling\n\nThe SDK uses a `Result\u003cT, Error\u003e` pattern instead of exceptions.\n\n```cpp\nauto result = client.validate(\"LICENSE-KEY\");\n\nif (result.is_ok()) {\n    auto\u0026 validation = result.value();\n    // Success - check validation.valid for license validity\n} else {\n    // Error - network, auth, or API error\n    std::cerr \u003c\u003c \"Error: \" \u003c\u003c result.error_message() \u003c\u003c \"\\n\";\n\n    switch (result.error_code()) {\n        case licenseseat::ErrorCode::NetworkError:\n            // No network connectivity\n            break;\n        case licenseseat::ErrorCode::LicenseNotFound:\n            // Invalid license key\n            break;\n        case licenseseat::ErrorCode::AuthenticationFailed:\n            // Invalid API key\n            break;\n        case licenseseat::ErrorCode::SeatLimitExceeded:\n            // Too many activations\n            break;\n        // ... handle other cases\n    }\n}\n```\n\n### Error Codes\n\n| Code                    | Description                                 |\n| ----------------------- | ------------------------------------------- |\n| `Success`               | Operation completed successfully            |\n| `NetworkError`          | HTTP request failed (no connectivity)       |\n| `ConnectionTimeout`     | Request timed out                           |\n| `SSLError`              | SSL/TLS error                               |\n| `InvalidLicenseKey`     | License key format is invalid               |\n| `LicenseNotFound`       | License key not found in system             |\n| `LicenseExpired`        | License has expired                         |\n| `LicenseRevoked`        | License has been revoked                    |\n| `LicenseSuspended`      | License is suspended                        |\n| `LicenseNotActive`      | License is not active                       |\n| `LicenseNotStarted`     | License hasn't started yet                  |\n| `SeatLimitExceeded`     | Maximum activations reached                 |\n| `ActivationNotFound`    | Device activation not found                 |\n| `DeviceAlreadyActivated`| Device is already activated                 |\n| `ProductNotFound`       | Product slug not found                      |\n| `ReleaseNotFound`       | Software release not found                  |\n| `AuthenticationFailed`  | Invalid or missing API key                  |\n| `PermissionDenied`      | API key lacks required permissions          |\n| `MissingParameter`      | Required parameter not provided             |\n| `InvalidParameter`      | Parameter value is invalid                  |\n| `ValidationFailed`      | Generic validation failure                  |\n| `ServerError`           | Server-side error (5xx)                     |\n| `FeatureNotConfigured`  | Feature not enabled for product             |\n| `SigningNotConfigured`  | Offline signing not configured              |\n| `ParseError`            | Failed to parse response                    |\n| `InvalidSignature`      | Cryptographic signature invalid             |\n| `FileError`             | File read/write error                       |\n| `FileNotFound`          | File not found                              |\n| `Unknown`               | Unknown error                               |\n\n---\n\n## Platform Support\n\n| Platform | Compiler                      | Status    |\n| -------- | ----------------------------- | --------- |\n| Linux    | GCC 9+, Clang 10+             | Supported |\n| macOS    | Apple Clang 12+ (ARM \u0026 Intel) | Supported |\n| Windows  | MSVC 2019+                    | Supported |\n\n### Device Identification\n\nThe SDK automatically generates a unique device identifier:\n\n- **macOS**: IOKit Platform UUID\n- **Windows**: Machine GUID from registry\n- **Linux**: `/etc/machine-id` or hostname-based fallback\n\n---\n\n## Testing\n\n```bash\ncmake -B build -DLICENSESEAT_BUILD_TESTS=ON\ncmake --build build\n./build/tests/licenseseat_tests\n```\n\n```\n[==========] 236 tests from 67 test suites ran. (210 ms total)\n[  PASSED  ] 236 tests.\n```\n\n### Test Suites\n\nThe SDK includes comprehensive tests:\n\n| Test Suite           | Tests | Network | Description                                 |\n| -------------------- | ----- | ------- | ------------------------------------------- |\n| Unit tests           | 236   | No      | Core functionality, parsing, crypto         |\n| Crypto stress tests  | 44    | Yes     | Ed25519, Base64, offline token verification |\n| Integration tests    | 48    | Yes     | Full API testing                            |\n| Scenario tests       | 38    | Yes     | Real-world workflows (10 scenarios)         |\n\n### Running Integration Tests\n\nThe integration, crypto stress, and scenario tests require a live LicenseSeat account. Configure credentials via environment variables:\n\n```bash\n# Set your credentials\nexport LICENSESEAT_API_KEY=\"ls_your_api_key\"\nexport LICENSESEAT_PRODUCT_SLUG=\"your-product\"\nexport LICENSESEAT_LICENSE_KEY=\"XXXX-XXXX-XXXX-XXXX\"\n\n# Build the tests\ncmake -B build -DLICENSESEAT_BUILD_TESTS=ON\ncmake --build build\n\n# Run integration tests\n./build/tests/integration_test      # 48 tests - Full API coverage\n./build/tests/crypto_stress_test    # 44 tests - Crypto \u0026 offline tokens\n./build/tests/scenario_test         # 38 tests - Real-world scenarios\n```\n\n### Scenario Test Coverage\n\nThe scenario tests validate 10 real-world use cases matching the Swift SDK test coverage:\n\n1. **First app launch \u0026 activation** – Fresh install, validation, activation\n2. **Returning user** – Cached license, session persistence\n3. **Offline mode** – Offline token generation and verification\n4. **Security** – Fake keys, wrong product, invalid API key, tampered signatures\n5. **License persistence** – State consistency during session\n6. **Grace period \u0026 expiration** – Token expiry handling\n7. **Deactivation flow** – Device removal, seat freeing\n8. **Re-activation** – Multi-device scenarios, seat limits\n9. **Auto-validation** – Background refresh cycles\n10. **Event system** – Event subscriptions and callbacks\n\n---\n\n## Project Structure\n\n```\nlicenseseat-cpp/\n├── include/licenseseat/     # Public headers\n├── src/                     # Implementation\n├── deps/                    # Vendored dependencies\n│   ├── ed25519/            # Ed25519 signatures\n│   └── PicoSHA2/           # SHA-256 (header-only)\n├── integrations/\n│   ├── unreal/             # Unreal Engine plugin\n│   └── juce/               # JUCE/VST integration\n├── tests/                   # Unit tests\n└── examples/               # Usage examples\n```\n\n---\n\n## License\n\nMIT License – see [LICENSE](LICENSE) for details.\n\n---\n\n## Links\n\n- [LicenseSeat Website](https://licenseseat.com)\n- [Documentation](https://licenseseat.com/docs)\n- [API Reference](https://licenseseat.com/docs/api)\n- [GitHub Issues](https://github.com/licenseseat/licenseseat-cpp/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flicenseseat%2Flicenseseat-cpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flicenseseat%2Flicenseseat-cpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flicenseseat%2Flicenseseat-cpp/lists"}