Lizenz-Schnittstelle
Ein kostenpflichtiges Plugin fragt beim Start hier nach, ob der eingetragene Schlüssel gültig ist. Zwei Parameter, eine Antwort.
Der Aufruf
Erreichbar per GET und POST, ohne Anmeldung.
Der Schlüssel selbst ist das Geheimnis.
Die Schnittstelle antwortet auf Englisch. Sie wird von Plugins angesprochen, deren Konsolen und Protokolle englisch sind — Feldnamen, Codes und Meldungen sind deshalb durchgehend englisch. Die Oberfläche des Hubs bleibt deutsch.
GET …/api/license/check?key=CD-XXXXX-XXXXX-XXXXX-XXXXX&plugin=mein-plugin
POST …/api/license/check
Content-Type: application/json
{"key":"CD-…","plugin":"mein-plugin","server":"mc-01","nonce":"a1b2c3"}
Parameter
| Name | Pflicht | Bedeutung |
|---|---|---|
key | ja | Der Lizenzschlüssel. Groß-/Kleinschreibung und Bindestriche sind egal. |
plugin | ja | Der Slug deines Plugins, so wie er in der Adresse steht. |
server | nein | Kennung des Servers. Wird beim ersten Mal an die Lizenz gebunden und belegt einen Platz. Ohne diesen Wert wird nur Auskunft gegeben. |
serverName | nein | Klarname für die Übersicht des Kunden, z. B. „Lobby". |
nonce | empfohlen | Zufallswert, der in der Antwort und in der Unterschrift wieder auftaucht. Damit lässt sich eine aufgezeichnete Antwort nicht erneut einspielen. |
Die alten Pfade /api/lizenz/pruefen
und der Parameter schluessel funktionieren weiterhin.
Die Antwort
{
"ok": true,
"valid": true,
"reason": "ok",
"message": "License is valid.",
"plugin": { "slug": "mein-plugin", "name": "Mein Plugin", "price": "4,50 €" },
"owner": "Steve",
"type": "plugin",
"expires": null,
"issued": "2026-09-22T10:12:00.000Z",
"server": { "id": "mc-01", "since": "2026-09-22T10:12:00.000Z" },
"time": "2026-09-22T10:40:11.000Z",
"nonce": "a1b2c3",
"signature": "9f86d081…"
}
Wichtig: Auch eine abgelehnte Lizenz kommt mit
Status 200 zurück — die Entscheidung steht in valid. Ein Fehlercode
würde in vielen Plugins als „Hub nicht erreichbar" durchgehen, und dann liefe ein
gesperrter Schlüssel einfach weiter. Nur 400 (Parameter fehlt) und
429 (zu viele Anfragen) sind echte Fehler.
Mögliche Gründe
reason | Bedeutung |
|---|---|
| ok | Alles in Ordnung. |
| format | Der Schlüssel hat nicht das erwartete Format. |
| unknown | Diesen Schlüssel gibt es nicht. |
| blocked | Der Schlüssel wurde gesperrt. |
| expired | Der Schlüssel ist abgelaufen. |
| mismatch | Der Schlüssel gehört zu einem anderen Plugin. |
| no_plugin | Das angefragte Plugin ist hier nicht bekannt. |
| owner_blocked | Das Konto des Inhabers ist gesperrt. |
| not_admin | Generalschlüssel, dessen Inhaber kein Administrator mehr ist. |
| server_limit | Die erlaubte Zahl an Servern ist erreicht. |
| rate_limit | Zu viele Anfragen in kurzer Zeit. |
| missing_key | Der Parameter key fehlt. |
Unterschrift prüfen
Jedes kostenpflichtige Plugin hat ein eigenes Geheimnis — du findest es unter Meine Lizenzen. Damit wird die Antwort signiert:
signature = HMAC_SHA256(secret, key + "|" + plugin + "|" + (valid ? "1" : "0")
+ "|" + time + "|" + nonce)
key ist dabei die Normalform
(Großbuchstaben, mit Bindestrichen), plugin der Slug, time
der Zeitstempel aus der Antwort. Stimmt die
Unterschrift nicht, hat jemand die Antwort unterwegs ausgetauscht — etwa durch einen
Eintrag in der hosts-Datei. Ohne diese Prüfung ist die Lizenzabfrage
in wenigen Minuten umgangen.
Beispiel für ein Paper-Plugin
public final class Lizenz {
private static final String HUB = "…";
private static final String PLUGIN = "mein-plugin";
private static final String SECRET = "…"; // steht unter /lizenzen
/** Gibt true zurück, wenn der Schlüssel gültig ist und die Antwort echt war. */
public static boolean pruefen(String key, String serverId) throws Exception {
String nonce = Long.toHexString(new SecureRandom().nextLong());
URL url = new URL(HUB + "/api/license/check"
+ "?key=" + URLEncoder.encode(key, "UTF-8")
+ "&plugin=" + PLUGIN
+ "&server=" + URLEncoder.encode(serverId, "UTF-8")
+ "&nonce=" + nonce);
HttpURLConnection verb = (HttpURLConnection) url.openConnection();
verb.setConnectTimeout(5000);
verb.setReadTimeout(5000);
JsonObject antwort = JsonParser.parseReader(
new InputStreamReader(verb.getInputStream(), StandardCharsets.UTF_8)).getAsJsonObject();
boolean valid = antwort.get("valid").getAsBoolean();
String time = antwort.get("time").getAsString();
// key in Normalform: Großbuchstaben, mit Bindestrichen
String erwartet = hmac(SECRET, key.toUpperCase()
+ "|" + PLUGIN + "|" + (valid ? "1" : "0") + "|" + time + "|" + nonce);
if (!erwartet.equals(antwort.get("signature").getAsString())) return false;
if (!nonce.equals(antwort.get("nonce").getAsString())) return false;
return valid;
}
private static String hmac(String secret, String text) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] roh = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : roh) sb.append(String.format("%02x", b));
return sb.toString();
}
}
Serverplatz freigeben
Beim Umzug auf eine andere Maschine gibt dieser Aufruf den belegten Platz wieder frei. Der Kunde kann das auch selbst unter Meine Lizenzen tun.
POST …/api/license/release
{"key":"CD-…","server":"mc-01"}
