Install
The C++ SDK licenses native desktop apps, games, CLI tools, Unreal Engine projects, and JUCE audio plugins. It has a header-only core with zero dependencies and verifies licenses identically to the Swift, Rust, and JavaScript SDKs (proven by cross-SDK conformance vectors). Requires C++17 or later.
Install
Section titled “Install”CMake — FetchContent (recommended)
Section titled “CMake — FetchContent (recommended)”include(FetchContent)FetchContent_Declare( keylight GIT_REPOSITORY https://github.com/keylight-dev/keylight-cpp.git GIT_TAG v0.1.0)FetchContent_MakeAvailable(keylight)
# Link against the header-only core (zero dependencies):target_link_libraries(MyApp PRIVATE keylight::keylight)
# Optional: enable the built-in HTTPS transport (needs cpp-httplib + OpenSSL):# set(KEYLIGHT_BUILD_HTTPLIB_TRANSPORT ON)# target_link_libraries(MyApp PRIVATE keylight::httplib_transport)Single-header drop-in
Section titled “Single-header drop-in”Download keylight_single.hpp from the v0.1.0 GitHub Release and place it anywhere on your include path. No CMake required — add #include "keylight_single.hpp" and you’re done.
vcpkg install keylight[httplib-transport]conan install --requires="keylight/0.1.0"Transport
Section titled “Transport”The core is network-agnostic. Supply any transport that satisfies the one-method concept (get(url) -> std::string). The opt-in HttplibTransport wraps cpp-httplib + OpenSSL:
set(KEYLIGHT_BUILD_HTTPLIB_TRANSPORT ON) # enable before FetchContent_MakeAvailable#include <keylight/httplib_transport.hpp>keylight::HttplibTransport transport;Bring your own transport by subclassing keylight::Transport — useful when you already have a network layer (e.g. FHttpModule in Unreal, juce::URL in JUCE).
Initialise
Section titled “Initialise”Fetch the tenant’s trusted keyset once so leases verify offline, then construct the client:
#include <keylight/keylight.hpp>#include <keylight/httplib_transport.hpp>
// 1. Fetch the trusted Ed25519 keyset (or pin keys in Config directly).keylight::HttplibTransport transport;auto keys = keylight::fetchKeyset(transport, "https://api.keylight.dev", "your-tenant");
// 2. Configure the client.keylight::Config cfg;cfg.tenantId = "your-tenant";cfg.productId = "your-product";cfg.sdkKey = "sdk_live_…"; // from your Keylight dashboardcfg.trustedKeys = keys.value_or(std::map<std::string, std::string>{});cfg.maxOfflineDays = 7; // optional offline grace window
// 3. Resolve the default lease store path and construct.auto storePath = keylight::default_store_path(cfg);keylight::FileStore store(storePath);keylight::Client kl(cfg, transport, store);
// 4. Run the on-launch check (validates online if needed, falls back to cached lease).kl.checkOnLaunch();Activate and gate features
Section titled “Activate and gate features”// Activate a license key (online). The returned lease is Ed25519-verified// before anything is persisted.auto res = kl.activate("USER-LICENSE-KEY");if (!res.activated) { // res.error contains the reason}
// Gate features on entitlements — works offline from the cached lease.if (kl.hasEntitlement("pro")) { // unlock pro features}
// Check the current license state.switch (kl.state()) { case keylight::State::Licensed: break; case keylight::State::Trial: break; case keylight::State::Expired: break; case keylight::State::Invalid: break;}
// Release the seat when uninstalling / switching machines.kl.deactivate();Dashboard-owned settings
Section titled “Dashboard-owned settings”The Trial length and Free tier set in the dashboard are the values that apply; what you compile in is the seed a fresh install uses first. Read what is in force with kl.effectiveTrialDurationDays() and kl.effectiveFreeTierEnabled(); kl.fetchConfig() asks the server now instead of waiting for the next validate or beacon.
To verify that those settings really came from your dashboard, pin your key and turn on the check (0.2.2+ for the behaviour described here):
cfg.trustedKeys = { { "k1", "<raw ed25519 public key, base64>" } }; // pinned, not fetchedcfg.requireSignedConfig = true;Leave it off until your app has a trial length set in the dashboard. That is what makes Keylight sign an app’s settings; with the switch on, an unsigned app rejects every legitimate response and stays on the value you compiled in. Once it is on, settings that do not verify against the keys you compiled in are never cached, on every route they arrive on. Pin the key in your build rather than fetching it: a keyset fetched over the same connection can be forged by the same attacker. See Verify the signature.
After a purchase
Section titled “After a purchase”Payment webhooks can lag the checkout redirect. refreshAfterUpgrade polls validate briefly and returns true as soon as the entitlements or state change, false on timeout or when no license is stored. It blocks for up to the timeout; refreshAfterUpgradeAsync returns a std::future<bool> instead:
// When the customer comes back from checkout. Milliseconds; defaults shown.auto fut = kl.refreshAfterUpgradeAsync(30000, 2000);Unreal Engine
Section titled “Unreal Engine”The SDK ships a first-party Unreal adapter built on FHttpModule. Add keylight-cpp as a third-party module and use UKeylightSubsystem:
// In your GameInstance / GameMode (game thread):if (auto* KL = GetGameInstance()->GetSubsystem<UKeylightSubsystem>()){ TMap<FString, FString> TrustedKeys; TrustedKeys.Add(TEXT("k1"), TEXT("<base64-public-key>")); KL->Configure(TEXT("your-tenant"), TEXT("your-game"), TrustedKeys);
// OnActivateComplete fires on the game thread: (bSuccess, State, Message). KL->OnActivateComplete.AddDynamic(this, &AYourClass::OnActivated); KL->Activate(LicenseKey);}
// Mark UFUNCTION() in your header so AddDynamic can bind it:void AYourClass::OnActivated(bool bSuccess, EKeylightState State, const FString& Message){ if (State == EKeylightState::Licensed) { /* unlock pro content */ }}UKeylightSubsystem routes all HTTP through FHttpModule — no third-party HTTP dependency needed. Activation runs on a background thread and the completion delegate is marshaled back to the game thread.
JUCE audio plugins
Section titled “JUCE audio plugins”The SDK ships a JUCE adapter over juce::URL. The adapter is audio-thread-safe: network calls happen on a background thread; entitlement reads are lock-free via a std::atomic-backed cache.
// In your PluginProcessor (message thread):keylight::juce_integration::Licensing licensing(cfg); // uses juce::URL transportlicensing.checkOnLaunch(); // async, non-blocking
// On the audio thread (safe — lock-free atomic read):bool isPro = licensing.hasFeature("pro");Use the single-header drop-in to keep your plugin build self-contained: no CMake, no extra submodule, one #include.
- Offline verification — verify a signed lease with no network round-trip.
- GitHub · v0.1.0 Release