Client SDKs
BetaAdd LicenseX to your loader
Both SDKs wrap the LicenseX /api/v1/* endpoints. Download the source files below, drop them into your project, initialise a client with your app credentials, and call Authenticate(). Every response is HMAC-SHA256-verified before your code sees it — the same way KeyAuth's SDKs work, except the server is yours.
Installation
Both SDKs are plain source files — download them, drop them into your project, and they work. No package manager, no build step to run first, nothing to install.
// LicenseX C++ SDK
// Copy this file and licensex.cpp into your project. Requires C++17 and
// links against winhttp.lib and bcrypt.lib (Windows only).
//
// Every response from the server (except InvalidCredentials, which the
// server can't sign — see below) carries an HMAC-SHA256 signature computed
// with your app's api_secret. This SDK verifies that signature before
// trusting anything in the response body, so a patched/cracked client
// would need to recover api_secret itself, not just flip a boolean.
#pragma once
#include <string>
#include <optional>
namespace licensex {
enum class ErrorCode {
None,
InvalidCredentials, // app_id/api_secret rejected. Sent UNSIGNED — the
// server has no proof you're who you claim to be,
// so there's nothing to sign the response with.
ServerRejected, // Verified success:false — key is genuinely
// invalid/expired/banned/revoked, or (for
// ResetHwid) the reset limit was hit.
SignatureInvalid, // Response body's HMAC didn't match. Tampered in
// transit, or api_secret doesn't match the
// dashboard. NEVER trust auth.ok when this fires.
ResetLimitReached, // ResetHwid() specific: resetsUsed/resetsAllowed
// on the result say exactly where you stand.
NotFound, // GetVariable() specific: no variable with that
// name exists for this app.
RateLimited, // Too many requests — back off and retry.
NetworkError, // Request never got a response.
Timeout, // Server didn't respond in time.
ParseError, // Response body wasn't valid JSON.
};
struct LicenseInfo {
std::string status; // "active" | "expired" | "banned" | "unused"
std::optional<std::string> expiresAt; // nullopt = lifetime license
};
struct AuthResult {
bool ok = false;
ErrorCode error = ErrorCode::None;
std::string errorMessage;
LicenseInfo license;
};
// ResetResult, VariableResult, LogResult, GetHardwareId(), and the full
// Client class declaration continue below — see licensex.hpp for the
// complete file (110 lines).
class Client {
public:
Client(std::string baseUrl, std::string appId, std::string apiSecret);
AuthResult Authenticate(const std::string& licenseKey, const std::string& hwid);
ResetResult ResetHwid(const std::string& licenseKey);
VariableResult GetVariable(const std::string& name);
LogResult Log(const std::string& message, const std::string& licenseKey = "");
private:
std::string baseUrl_, appId_, apiSecret_;
};
} // namespace licensexThen build:
g++ -std=c++17 your_loader.cpp licensex.cpp \
-I . -lwinhttp -lbcrypt -o loader.exe
# (Or add winhttp.lib and bcrypt.lib to your existing
# CMake / Visual Studio project's linker settings.)Initialise the client
Create one client instance at startup and keep it alive. It holds your credentials and an HTTP connection pool internally.
#include "licensex.hpp"
// Initialise once at startup — keep the client alive for the
// lifetime of your process. NOT thread-safe to construct concurrently.
licensex::Client client(
"https://your-licensex-deployment.example.com",
"app_your_app_id_here",
"your_api_secret_here" // load from encrypted storage in prod
);Authenticate a key
Pass the license key and hardware ID. Every response is HMAC-SHA256-verified before your code sees it — except a credential rejection, which is sent unsigned (there'd be no secret to sign it with). ServerRejected after a clean verify means the key is genuinely invalid; SignatureInvalid means don't trust the body at all.
std::string hwid = licensex::GetHardwareId();
std::string key = GetKeyFromUser(); // however your loader collects it
licensex::AuthResult auth = client.Authenticate(key, hwid);
if (!auth.ok) {
switch (auth.error) {
case licensex::ErrorCode::InvalidCredentials:
// app_id/api_secret rejected outright — server sends this
// UNSIGNED (see "Signature verification" below), so it never
// goes through the HMAC check at all.
ShowError("This build is misconfigured. Contact support.");
break;
case licensex::ErrorCode::ServerRejected:
// Verified "no" from the server — key is invalid/expired/banned
ShowError("Invalid license key.");
break;
case licensex::ErrorCode::SignatureInvalid:
// Response claimed success/failure but the HMAC didn't match —
// tampered in transit, or api_secret mismatch. Never trust
// auth.ok in this case, whatever the body said.
ShowError("Could not verify server response.");
break;
case licensex::ErrorCode::RateLimited:
ShowError("Too many attempts. Try again shortly.");
break;
default:
// NetworkError, Timeout, etc.
ShowError("Could not reach license server.");
break;
}
return false;
}
// auth.license.status → "active" | "expired" | "banned" | "unused"
// auth.license.expiresAt → std::optional<std::string> (nullopt = lifetime)
// That's the whole shape the server returns here — there's no per-license
// "note" field in the response, even though the dashboard lets you attach
// one to a license for your own reference.
UnlockApp();Hardware ID
GetHardwareId() derives a stable fingerprint from the CPU, machine GUID, and system drive serial. You pass it into Authenticate() — you're in control of when it's collected.
// What GetHardwareId() actually reads (stable across reboots):
// • CPU vendor + model string via CPUID
// • MachineGuid from HKLM\SOFTWARE\Microsoft\Cryptography
// • Volume serial number of the system drive
//
// The three values are concatenated and SHA-256'd into a fixed string.
// You never need to call this yourself for auth — Authenticate() takes
// it as a parameter so you control when/how you collect it.
std::string hwid = licensex::GetHardwareId();Resetting a device
Let a user clear their own device slots after a reformat or hardware upgrade, without you touching the dashboard. Capped per-license by a limit the app owner sets — this isn't a way around max devices.
// Self-service HWID reset — for when a user reformats or upgrades their
// PC and Authenticate() now fails with DeviceLimitReached. Posts
// { app_id, api_secret, license_key } to /api/v1/reset-hwid, which clears
// every device slot on that license so the next Authenticate() call
// re-registers this machine as if it were new.
//
// Bounded server-side by a per-app "max resets" setting the app owner
// controls from the dashboard — this is NOT unlimited, so don't let a
// user spam it hoping to dodge the device limit.
licensex::ResetResult reset = client.ResetHwid(key);
if (!reset.ok) {
switch (reset.error) {
case licensex::ErrorCode::ResetLimitReached:
// reset.resetsUsed / reset.resetsAllowed tell you exactly
// where they stand — show that, not just a flat refusal.
ShowError("Reset limit reached (" +
std::to_string(reset.resetsUsed) + "/" +
std::to_string(reset.resetsAllowed) +
"). Contact support.");
break;
case licensex::ErrorCode::ServerRejected:
ShowError("License is banned, revoked, or invalid.");
break;
default:
ShowError("Could not reach license server.");
break;
}
return false;
}
// Devices are cleared — call Authenticate() again to re-register this
// machine on the license.
RetryAuthenticate();Remote variables
Pull server-controlled values at runtime without recompiling. Minimum client version, feature flags, download URLs — change them from the dashboard instantly.
// Pull a server-controlled value at runtime — no recompile needed.
// Use this for things like minimum client version, feature flags,
// download URLs, or anything you want to change from the dashboard.
//
// Under the hood this posts { app_id, api_secret, name } to /api/v1/variable
// — same credentials as Authenticate(), just a different endpoint — and
// the response is signed the same way, so GetVariable() verifies it
// before ok is ever true.
licensex::VariableResult ver = client.GetVariable("min_client_version");
if (!ver.ok) {
// NotFound: no variable with that name exists for this app.
// SignatureInvalid / RateLimited / NetworkError: same meaning as
// in Authenticate() above.
return true; // fail open — don't block the user over a missing flag
}
if (IsOutdated(ver.value)) {
ShowError("Update required: " + ver.value);
return false;
}Client logging
Fire lightweight events to the dashboard logs. Tie each entry to a license key so you can see exactly what's happening per-user.
// Send a lightweight event to the dashboard logs.
// licenseKey ties the log entry to the right license in your app.
client.Log("loader started", licenseKey);
client.Log("feature_x accessed", licenseKey);Error codes
Only ServerRejected after a clean signature check means the key is genuinely bad. InvalidCredentials is the one exception that arrives unsigned by design — treat it as a config problem in your build, not a bad key. Everything else (SignatureInvalid, timeouts, network errors) means the check couldn't complete at all — don't treat those as a real auth failure.
| Code | When it fires |
|---|---|
InvalidCredentials | app_id/api_secret rejected outright. Sent unsigned — see "Signature verification" below — so it never reaches the HMAC check. |
ServerRejected | Server returned success:false after a verified signature — the key is genuinely invalid, expired, revoked, or banned. |
SignatureInvalid | HMAC check failed on a response that was supposed to be signed — response was tampered with in transit, or api_secret doesn't match the dashboard. Never trust the body's success/ok value when this fires. |
NotFound | Returned by GetVariable() when no variable with that name exists for the app. Not an error worth blocking on — decide a sensible fallback. |
ResetLimitReached | Returned by ResetHwid() once the license has used its allotted self-service resets. resetsUsed/resetsAllowed on the result tell you exactly where it stands. |
RateLimited | Too many requests in a short window — limits are per-IP, per-license-key, and per-app, and apply to every /api/v1/* endpoint. Back off and retry. |
NetworkError | HTTP request failed before a response arrived. |
Timeout | Server didn't respond within the deadline. |
ParseError | Response body wasn't valid JSON — usually a proxy or server error page. |
Both SDKs target Windows only at runtime. The C++ SDK requires C++17 and links against winhttp.lib and bcrypt.lib; it has been verified to compile cleanly with MinGW-w64. The C# SDK requires .NET 8 and has been verified to build cleanly with the .NET 8 SDK — retargeting to .NET Framework or netstandard2.0 mainly means swapping the HTTP and P/Invoke calls for their older equivalents. Neither SDK has been exercised against a live LicenseX deployment end-to-end; test against your own deployment before shipping.