Für Entwickler

Lizenz-Schnittstelle

Ein kostenpflichtiges Plugin fragt beim Start hier nach, ob der eingetragene Schlüssel gültig ist. Zwei Parameter, eine Antwort.

Fertige Bibliothek nehmen Meine Schlüssel

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

NamePflichtBedeutung
keyja Der Lizenzschlüssel. Groß-/Kleinschreibung und Bindestriche sind egal.
pluginja Der Slug deines Plugins, so wie er in der Adresse steht.
servernein Kennung des Servers. Wird beim ersten Mal an die Lizenz gebunden und belegt einen Platz. Ohne diesen Wert wird nur Auskunft gegeben.
serverNamenein Klarname für die Übersicht des Kunden, z. B. „Lobby".
nonceempfohlen 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

reasonBedeutung
okAlles in Ordnung.
formatDer Schlüssel hat nicht das erwartete Format.
unknownDiesen Schlüssel gibt es nicht.
blockedDer Schlüssel wurde gesperrt.
expiredDer Schlüssel ist abgelaufen.
mismatchDer Schlüssel gehört zu einem anderen Plugin.
no_pluginDas angefragte Plugin ist hier nicht bekannt.
owner_blockedDas Konto des Inhabers ist gesperrt.
not_adminGeneralschlüssel, dessen Inhaber kein Administrator mehr ist.
server_limitDie erlaubte Zahl an Servern ist erreicht.
rate_limitZu viele Anfragen in kurzer Zeit.
missing_keyDer 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"}