How to add license keys to a Velocity plugin
Short answer: add the SunLicense Java SDK (shaded and relocated), read the buyer's key from your plugin's data directory, check it on ProxyInitializeEvent through Velocity's scheduler, and switch your features off when SunLicense says no. A network error is not a no: keep running and check again later.
// build.gradle.kts: implementation("com.sunlicense:sunlicense:0.0.1"), shaded and relocated
private final SunLicense client = new SunLicense.Builder()
.baseUrl("https://api.sunlicense.com").token("slk_…").build(); // API key from your build, never git
private volatile boolean licensed;
@Subscribe
public void onProxyInitialize(ProxyInitializeEvent event) {
String key = loadConfig().getProperty("license-key", ""); // plugins/myplugin/config.properties
proxy.getScheduler().buildTask(this, () -> {
try {
SunLicense.Result r = client.validate(key, Map.of("host", machineId()));
licensed = r.getStatus() != 400; // only a 400 is SunLicense saying no
if (!licensed) logger.error("MyPlugin license rejected: {}", r.getBody());
} catch (SunLicense.SunLicenseException e) {
licensed = true; // unreachable: keep running, check again later
}
}).schedule();
}Velocity runs scheduler tasks on its own thread pool, so the network call never holds up the proxy. Your listeners and commands start with if (!licensed) return;.
Start from the template instead: example-velocity-plugin (Velocity 3.5 and 4.x, Java 21) has this plus a stable machine id, masked keys, re-checks every 6 hours, a signed offline license fallback, BuiltByBit placeholders, a sealed core, /myplugin status and recheck commands, tests and CI. All templates.
Or let your AI agent do it: paste the setup prompt into your coding agent.
Free plan: 3 products, 100 licenses, no credit card. Create a free account.
Step by step
- In the dashboard: a product, a policy (a machine is a proxy here, so 1 machine fits a single network) and an API key, which can only validate licenses.
- Add the SDK and relocate it with Shadow (
relocate("com.sunlicense", "com.example.myplugin.libs.sunlicense")) so plugins with different SDK versions don't clash. - Keep the API key out of git:
~/.gradle/gradle.properties, expanded into a resource at build time. - The buyer's key:
config.propertiesin the@DataDirectoryfolder, or a%%__SUNLICENSE__%%placeholder that BuiltByBit and Voxel Shop fill in on download. - Machine id as
host: a hash of OS, architecture and host name (snippet in the Paper guide). - Results: 200 runs your features, 400 stops them with the reason (keys masked in logs), anything else keeps running and retries in about 10 minutes or uses an offline license. Re-check with
.repeat(6, TimeUnit.HOURS)so a revoked key reaches running proxies.
Selling it
One proxy usually fronts several backend servers. License the proxy plugin per proxy (the machine limit counts proxies), and if you also sell a Paper companion plugin, give it its own product or policy so each counts its own machines. Stores: BuiltByBit, Voxel Shop, Tebex, SpigotMC and the others.
Going further
- Protection and obfuscation for the jar you sell.
- Paper and Fabric versions of this guide. More on licensing Minecraft plugins.
