License activation for desktop apps and audio plugins, in C++.
Ship a paid plugin or app and you need the same four things: let a customer unlock it, keep it unlocked offline, tie the seat to a machine, and hand the seat back when they move. This repo is those four things for C++, as a header-only core library and a drop-in JUCE module that adds a finished activation UI on top of it.
The JUCE module's activation UI, themed for a fictional plugin. Your logo, your colours, your copy.
| What it is | Where | |
|---|---|---|
| Core SDK | Header-only C++17 library: activation, polling, local RS256 validation, offline licenses, revocation, pluggable storage and HTTP. No framework. | include/moonbase/ |
moonbase_licensing |
Drop-in JUCE module. The core SDK plus a themeable activation UI, in-app updates, and zero third-party dependencies. | modules/moonbase_licensing/ |
OnlineUnlockStatus bridge |
Copy-paste reference header that drives juce::OnlineUnlockStatus from Moonbase. You supply the UI. |
examples/juce/ |
| Fingerprint spec | The normative, language-neutral device id algorithm every Moonbase SDK implements, with conformance vectors. | FINGERPRINT_SPEC.md |
| Core SDK | moonbase_licensing module |
OnlineUnlockStatus bridge |
|
|---|---|---|---|
| Form | Header-only library | Drop-in JUCE module | Copy-paste reference header |
| Built-in UI | No | Yes, themeable and animated | No, you build it |
| Requires | CMake 3.20, C++17 | JUCE 6.1.3+, C++17 | JUCE 7+, plus the core SDK |
| Third-party deps | CURL, OpenSSL, nlohmann_json | None | Inherits the core SDK's |
| Best for | Non-JUCE apps, CLI tools, your own frontend | New JUCE plugins that want a ready-made UI, including HISE projects | Projects already on OnlineUnlockStatus |
| Guide | core-sdk.md |
juce-module.md |
juce.md |
All three compute the same device id, so a license activated through one validates in the others.
include(FetchContent)
FetchContent_Declare(moonbase_cpp
GIT_REPOSITORY https://github.com/Moonbase-sh/moonbase-cpp.git
GIT_TAG v4.4.0)
FetchContent_MakeAvailable(moonbase_cpp)
target_link_libraries(your_app PRIVATE moonbase::licensing)#include <moonbase/moonbase.hpp>
moonbase::licensing_options options;
options.endpoint = "https://your-tenant.moonbase.sh";
options.product_id = "your-product";
options.public_key = embedded_public_key_pem;
moonbase::licensing licensing(options);
auto request = licensing.request_activation(); // send the user to request.browser_urlThen poll, validate and persist: core SDK guide.
juce_add_module(path/to/moonbase-cpp/modules/moonbase_licensing)
target_link_libraries(MyPlugin PRIVATE moonbase_licensing)#include <moonbase_licensing/moonbase_licensing.h>
using namespace moonbase::juce_integration;
ActivationConfig config;
config.endpoint = "https://your-tenant.moonbase.sh";
config.productId = "your-product";
config.publicKey = embeddedPublicKeyPem;
addAndMakeVisible(activation = std::make_unique<ActivationComponent>(config));Three fields and one component, and every screen below is wired up: module overview, full guide.
Copy examples/juce/MoonbaseJuceBridge.h into
your project and use MoonbaseUnlockStatus wherever you use
juce::OnlineUnlockStatus today: bridge guide.
Trials and in-app updates, both built into the module.
- Browser activation. The app asks for a request, opens a URL, and polls. No serial numbers to type, no keyfiles to email.
- Offline activation. Either mint an offline license through the normal browser flow, or, on a machine with no network at all, exchange a device token file for a license token file. Both produce a permanent, locally-validated license.
- Local-first validation. Signature, audience, issuer, device and expiry are checked in-process. The API is contacted at most every few minutes, and a configurable grace period keeps a plugin working through an outage.
- Cross-SDK device identity. A SHA-256 of stable native hardware identifiers, per the shared spec. No subprocess, no root-only file, so it works inside an App Sandbox and a plugin host and reads the same elevated or not.
- Trials, seats and entitlements. Trial state, seat counts, expiry, sub-product ownership and custom properties all arrive on the validated license, so you can gate on more than a boolean.
- In-app updates. The module surfaces a newer entitled release, shows its notes, and downloads the installer for the user's platform with progress.
Two real JUCE 8 projects built as reference integrations. Their knobs deliberately do not process audio; the point is the licensing workflow wrapped around a real plugin.
DRIFT by Corino (VST3 / AU /
Standalone) is the native module reference. The processor owns a headless
ActivationController, LicenseGate fades to silence off a lock-free flag in
processBlock, and the editor shows ActivationComponent as a modal overlay. Every
connection, branding, trial and telemetry field lives in one shared factory:
src/Licensing.h.
HALO by Corino (standalone app) is the
bridge reference: a synchronous local check on startup, background re-validation,
timer-polled activation, and a "Sign out" item wired to revocation with a local-only
fallback. It vendors the bridge header verbatim from this repo:
src/license/HaloLicenseBridge.cpp.
| Core SDK | Install, CMake options, activation, validation, revocation, offline licenses, custom storage |
| JUCE module | Setup, the flow, gating, theming, updates, diagnostics, telemetry |
OnlineUnlockStatus bridge |
Wiring, async activation, deactivation, gating, metadata |
| Device identity | What the device id is made of, failure modes, diagnostics |
| Fingerprint spec | The normative cross-SDK algorithm and stability contract |
| Security | What the SDK guarantees, what it leaves to you, and how to gate well |
| Migrating from 3.x | The 4.0.0 device id change, and how to migrate a shipped fleet |
| Contributing | Build, test, generated files, CI, releases |
| Samples | core, JUCE module, bridge, UI snapshots |
This SDK answers one question soundly: is this license valid, for this product, on this machine, right now? It deliberately ships no anti-debugging, obfuscation or tamper detection, because anything general enough to live in an open-source header is identical in every plugin using it, and one published bypass would apply to all of them. Hardening belongs in your binary. How to do it well.
Semantic versioning, tagged v<major>.<minor>.<patch>. Source archives are published
per tag at
https://github.com/Moonbase-sh/moonbase-cpp/archive/refs/tags/v<version>.tar.gz, and
the installed CMake package declares SameMajorVersion compatibility.
Released under the MIT License.


