Licenses
Licenses
| License | Status | Product | IP limit | HWID limit | Expires at | Created at | |
|---|---|---|---|---|---|---|---|
| Loading... | |||||||
Products
Products
| Name | Version | Licenses | Created at | Updated at | |
|---|---|---|---|---|---|
| Loading... | |||||
Docs
Integrating license checks into a plugin
One reference page you can follow every time you wire up license checking in a new
plugin. Replace your-domain.com and the license key with your real values.
1. Verify endpoint
Call this from your plugin whenever you need to check a license:
GET https://your-domain.com/api/license/{licenseKey}/verify?challenge={timestamp}
| Param | Required | Description |
|---|---|---|
challenge | Recommended | Current time in milliseconds. Signed back by the server so you can verify the response is authentic. |
hwid | If HWID limit is set | A stable identifier for the machine (e.g. a hash of hardware info). |
metadata | Optional | Any string you want logged with this verification attempt. |
2. Example request
curl "https://your-domain.com/api/license/XXXX-XXXX-XXXX-XXXX-XXXX/verify?challenge=1735689600000"
3. Response
{ "valid": true, "result": "VALID", "signedChallenge": "MEUCIQD..." }
| Result | Meaning |
|---|---|
VALID | License is valid, use the software normally. |
NOT_FOUND | No license with this key exists. |
NOT_ACTIVE | License was suspended by the admin. |
EXPIRED | Past its expiration date. |
IP_LIMIT_EXCEEDED | Too many different IPs in the last 12 hours. |
HWID_LIMIT_EXCEEDED | Too many different devices activated. |
RATE_LIMIT_EXCEEDED | Verification requests happening too fast. |
4. Verifying the signature (recommended)
Fetch the public key once (GET /api/license/public-key) and bundle it with
your plugin at build time. Then verify signedChallenge against the exact
challenge string you sent - this proves the response really came from your
server and wasn't forged by the end user.
Java
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
PublicKey loadPublicKey(String pem) throws Exception {
String base64 = pem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(base64);
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encoded));
}
boolean verifySignature(String challenge, String signedChallengeBase64, PublicKey publicKey) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(challenge.getBytes("UTF-8"));
return sig.verify(Base64.getDecoder().decode(signedChallengeBase64));
}
Node.js
const crypto = require("crypto");
function verifySignature(challenge, signedChallengeBase64, publicKeyPem) {
return crypto.verify(
"sha256",
Buffer.from(challenge, "utf8"),
publicKeyPem,
Buffer.from(signedChallengeBase64, "base64")
);
}
5. Security note
If your plugin can call your own backend, do the license check there - it's the most secure option. If the check has to happen purely on the client side (no backend of your own), the challenge-response signature above is what protects you against a modified client simply claiming "valid: true".