AnziLock
AnziLock is the licensing system behind the paid ajdnikov plugins and AjdnikovPanel. It was built for these plugins rather than bolted on from a marketplace: one service checks licence keys for every plugin, hands keys to buyers through Discord, runs the licence portal where you manage your keys and downloads, and carries the AjdnikovPanel dashboard to your browser. This page explains how it works from the customer's side — what happens when you buy, what your server sends, and what to do when a check fails.
What AnziLock Is
AnziLock is one self-hosted service at license.ajdnikov-dev.com with several parts that share the same accounts and licences:
| Part | What it does for you |
|---|---|
| Verification API | Plugins check their key with it on start-up and on a heartbeat. Every answer is signed, so a plugin can tell a real answer from a forged one. |
| Licence portal | Sign in with Discord to see your licences, keys, bound IPs and servers, reset IPs, claim purchases, download releases and open support tickets. |
| Discord bot | /verify, /licenses, /claim, /reset-ips and /ticket in the ajdnikov Discord, plus automatic customer roles and key delivery by DM. |
| BuiltByBit link | Each purchase on BuiltByBit creates a licence waiting to be claimed. Keys are never baked into the jar. |
| Networks | Where the AjdnikovPanel dashboard lives. Your hub connects out to AnziLock, so there is no dashboard port, password or tunnel on your side. |
| Phone app | An Android app for the portal with notifications for licences, claims, ticket replies, new versions and network status. |
Bundles
One licence key can unlock several products. It verifies for each of them, grants each product's Discord roles and downloads, and shares one set of IP and server limits.
Getting Your Key
Keys look like ANZI-XXXXX-XXXXX-XXXXX-XXXXX. After you buy a plugin on BuiltByBit, BuiltByBit tells AnziLock about the purchase and an unclaimed licence is created for that plugin, tagged with your BuiltByBit username. You then claim it in one of three ways:
| Where | How |
|---|---|
| Discord ticket | Press Claim a purchase on the support panel in the Discord. A private ticket opens; pick the product and enter your BuiltByBit username. |
/claim | In the Discord: /claim username:<BuiltByBit name> [product]. |
| Licence portal | Sign in and open Claim a purchase. |
The claim is matched to the waiting licence by BuiltByBit username (case-insensitive). Staff approve it, or — when the purchase can be confirmed against BuiltByBit's purchase list — it may be approved automatically. Once approved, the key is posted in your ticket behind a spoiler, sent to you by DM, linked to your portal account, and your customer roles are granted.
If no purchase matches, staff may ask for proof of purchase before creating your licence. A rejected claim is DMed to you with the reason.
Already have a key?
Run /verify key:ANZI-… in the Discord, or use Link a key in the portal. That links an unowned key to your Discord account (creating your portal account if you never signed in) and grants your roles. A key that already belongs to someone else is refused.
Putting the Key in a Plugin
Paste the key into the plugin's config file and restart the server. The setting is named in each plugin's manual — for example licenseKey in AjdnikovPanel's config.properties. On success the console shows:
[AnziLock] License valid (lifetime) · licensed to steve
[AnziLock] Update available: 1.3.0 (you run 1.2.0)
The second line only appears when a newer version is published. A licence with an end date prints expires 2027-01-31 instead of lifetime.
On failure the plugin prints a block with the result code, a message, and the key with most of it masked, then applies the product's failure action:
[AnziLock] ================================================
[AnziLock] License check failed: IP_LIMIT_REACHED
[AnziLock] <the reason>
[AnziLock] Key: ANZI-XXXXX-…
[AnziLock] ================================================
[AnziLock] Disabling YourPlugin.
With an empty key the plugin stops with NO_KEY before contacting anyone.
How a Check Works
A plugin checks its key when it starts (verify) and then again on a heartbeat (by default every 10 minutes). Each check is one HTTPS request:
Plugin AnziLock
│ POST /api/v1/client/verify │
│ {key, product, hwid, nonce, …} ───▶ │ blacklist → product → key → status → expiry
│ │ → IP / server / seat limits → record usage
│ ◀─── {payload, signature} │ payload = {code, valid, action, heartbeat,
│ │ grace, nonce, hwid, ts, …}
│ check signature, nonce, hwid, time │
│ VALID → cache the signed answer, schedule the next heartbeat
│ invalid → run the failure action (DISABLE / SHUTDOWN / WARN)
│ no reply → keep running on the cached answer for the grace period
AnziLock checks, in order: whether the IP or server is blacklisted, whether the product exists and is active, whether the key exists and belongs to that product (or a bundle that includes it), whether it is suspended or expired, and whether the IP, server and seat limits allow one more. The first check that fails decides the answer.
The answer is signed with AnziLock's private key. The plugin carries the matching public key, so it accepts only answers that really came from AnziLock, for this request, for this server, and no older than 15 minutes. See Security model.
On start-up the plugin tries up to 3 times with a timeout of a few seconds each, so a slow network delays start-up by seconds, not minutes.
Offline Grace & Failure Actions
When AnziLock can't be reached
Your server does not stop because the licence server has a bad minute. After every successful check the plugin stores the signed answer next to its data. If a later check cannot reach AnziLock, the plugin keeps running on that stored answer for the product's grace period (by default 1 hour) and logs how long is left:
[AnziLock] License server unreachable (…). Running on the last signed check for another 47 min.
When the grace period runs out without a successful check, the failure action runs. A server that has never verified successfully cannot run offline — the first start needs a connection.
Rate limits, replayed requests and malformed requests are not verdicts on your licence: the plugin keeps its previous state and tries again on the next heartbeat.
Failure actions
Each product chooses what happens when a licence is rejected. The choice travels inside the signed answer, so it cannot be changed on the way.
| Action | What happens |
|---|---|
DISABLE (default) | The plugin disables itself. The rest of the server keeps running. |
SHUTDOWN | The server (or proxy) shuts down. |
WARN | The failure is logged and the plugin keeps working. |
Limits & Resets
Every licence carries three limits. The defaults come from the product; staff can change them per licence.
| Limit | Counts | When it's full |
|---|---|---|
| IP limit | Distinct public IPs that have used the key. Unused IPs can expire on their own after a set number of hours if the product says so. | IP_LIMIT_REACHED — reset your IPs |
| Server limit | Distinct servers, identified by a hardware ID: a hash of the server folder, host name, network card addresses, operating system and user. | HWID_LIMIT_REACHED — ask staff to reset servers |
| Online at once | Servers running the key at the same time, measured by heartbeats within twice the heartbeat interval. | SEAT_LIMIT_REACHED — stop one, or wait for it to time out |
Resetting IPs yourself
Moved hosts, or your host changed your IP? Clear the bound IPs with /reset-ips in the Discord or Reset IPs on the licence in the portal. Self-service resets are allowed per product and have a cooldown (24 hours by default). Staff can reset IPs without a cooldown, and reset servers (hardware IDs), from a ticket.
Moving a server to a new folder or machine changes its hardware ID. If that pushes you over the server limit, open a ticket.
Result Codes
The code is printed in the console when a check fails, and listed per request on your licence in the portal.
| Code | Meaning | What to do |
|---|---|---|
VALID | The licence is good. | Nothing. |
NO_KEY | The config has no key. | Paste your key and restart. |
LICENSE_NOT_FOUND | No such key. | Copy the key again from /licenses or the portal; check for spaces. |
PRODUCT_NOT_FOUND, PRODUCT_MISMATCH | The key belongs to a different plugin. | Use the key bought for this plugin. |
PRODUCT_INACTIVE | The product is switched off. | Ask in the Discord. |
LICENSE_SUSPENDED | Staff suspended the licence. You get a DM when this happens. | Open a ticket. |
LICENSE_EXPIRED | The licence's end date has passed. Reminders are sent by DM a few days before. | Renew it, or ask in a ticket. |
IP_LIMIT_REACHED | Too many different IPs used the key. | /reset-ips or Reset IPs in the portal. |
HWID_LIMIT_REACHED | Too many different servers used the key. | Open a ticket to reset servers. |
SEAT_LIMIT_REACHED | Too many servers run the key right now. | Stop one, or wait for it to time out. |
IP_BLACKLISTED, HWID_BLACKLISTED | This IP or server is blocked. | Open a ticket. |
RATE_LIMIT, REPLAY, BAD_REQUEST | Not a verdict on the licence. | None; the plugin tries again on the next heartbeat. If it repeats, update the plugin. |
MAINTENANCE | AnziLock is in maintenance. | Watch the Discord. |
GRACE | Running on the stored answer while AnziLock is unreachable. | Check the server's outgoing HTTPS connection. |
GRACE_EXPIRED, NETWORK_ERROR | AnziLock was unreachable for longer than the grace period, or on the very first start. | Allow outgoing HTTPS to license.ajdnikov-dev.com. |
The Licence Portal
Open license.ajdnikov-dev.com and choose Continue with Discord. You see only your own licences.
| Page | What's there |
|---|---|
| My licenses | Each licence with its status, expiry and limits. Open one to see its bound IPs (partly masked) and servers, reset IPs, and — for AjdnikovPanel — jump to your network. |
| Claim a purchase | Claim a BuiltByBit purchase, as above. |
| Downloads | Releases of every product you hold a licence for, with changelogs. |
| Support | Open a ticket and follow and answer it without leaving the portal. The conversation is the same Discord ticket channel. |
| Networks | Your AjdnikovPanel networks, and those shared with you. See Networks. |
| Account | Your BuiltByBit username (used to match claims) and phone notifications. |
Roles
| Role | Can |
|---|---|
| Customer | See own licences, reset own IPs (if the product allows), claim purchases, download releases, open tickets. |
| Staff | Everything above for every customer, plus create, edit and suspend licences, review claims, manage blacklists and read the audit log. |
| Admin | Everything, plus products, releases, settings and the signing key. |
Discord Commands
The AnziLock bot runs in the ajdnikov Discord. All replies are ephemeral: only you see them.
| Command | What it does |
|---|---|
/verify key:<ANZI-…> | Links a key to your account and grants your roles. Refuses keys owned by someone else. |
/licenses | Lists your licences with status, expiry and limits. |
/claim username:<BuiltByBit name> [product] | Opens a purchase claim. Delivered instantly when the purchase can be confirmed automatically; otherwise staff review it. |
/reset-ips license:<pick> | Clears the licence's bound IPs, subject to the product's rules and cooldown. |
/ticket | Opens a private ticket channel. |
Roles
Each product can grant Discord roles. You hold a product's roles while you own at least one licence for it that is active, not expired and not suspended. Roles update the moment that changes, and are re-checked every 30 minutes.
Direct messages
AnziLock DMs you when a key is delivered (with the download link), a few days before a licence expires, when it expires, when it is suspended, and when a claim is rejected.
Tickets & Panels
Support panels in the Discord are messages with buttons. Each button opens a private ticket channel (visible to you, the bot and staff), opens an application form, starts a purchase claim, or links to a page. The Custom plugins panel asks five questions about the plugin you want and opens a ticket with your answers in it — see Custom Requests.
You can have one open ticket per kind of button, so an open support ticket does not block a plugin application. When a ticket is closed it is archived or removed from the Discord; the conversation is saved first, so staff can still read it.
Networks
The AjdnikovPanel dashboard is served by AnziLock, not by your machine. Your hub opens one outgoing WebSocket to AnziLock (the relay) and proves itself with its licence key and hardware ID. AnziLock answers with a welcome signed by the same key as licence answers, so a fake server cannot drive your hub. From then on AnziLock signs people in with Discord, decides who may open the network, and passes their requests down that socket.
- No port, password, address or tunnel on your side — only outgoing HTTPS (port 443).
- Every 15 seconds the hub reports players and server states for the network list. Nothing about individual players leaves the hub except while someone is looking at the dashboard.
- You can share a network with your staff as Viewer or Admin, and give AnziLock staff read-only support access for 1 hour to 7 days. Without a grant, staff see only whether your network is up, its players, servers and version.
- Every visit by someone other than the owner, every change, and every sharing change is written to the audit log.
Setup, sharing and the relay in detail: AjdnikovPanel manual → Licensing & Relay.
Phone App
The AnziLock Android app opens the portal and adds notifications. Turn them on inside the app under Account → Phone notifications and pick what you want to hear about:
| Notification | When | On by default |
|---|---|---|
| New licenses | A licence is added to your account. | yes |
| Expiry reminders | A licence is about to expire, or has expired. | yes |
| Suspensions | One of your licences is suspended. | yes |
| Purchase claims | Your claim is approved or rejected. | yes |
| Ticket replies | Staff reply to your ticket, or close it. | yes |
| New versions | A new version of a plugin you own is out. | no |
| Network status | One of your networks goes offline or comes back. | yes |
Notifications arrive instantly by push, and the app also checks about every 15 minutes and whenever it is opened. You can pin up to 5 networks: each stays in the notification shade with its status, players and servers, and has an Unpin button.
Notifications are only stored for accounts with a phone switched on, and are deleted after 14 days. Switching the phone off in the app revokes its access; phones not heard from in 90 days are removed.
Security Model
The check itself
- Every answer, valid or not, is signed with Ed25519. The plugin embeds the public key and rejects anything unsigned or mis-signed.
- The plugin sends a fresh random nonce with every request. The answer must echo it, and AnziLock refuses a nonce it has seen in the last 10 minutes, so old answers cannot be replayed.
- The answer echoes the server's hardware ID, so an answer for one server cannot be reused on another.
- The answer carries a timestamp; the plugin rejects answers older than 15 minutes.
- The stored offline answer is the last signed valid answer, bound to the key and hardware ID, and honoured only for the grace period.
- The failure action is part of the signed answer, so it cannot be downgraded on the way.
The service
- Licence keys are stored encrypted (AES-256-GCM), looked up by a keyed hash. A database dump alone does not reveal keys.
- Portal sessions are random 256-bit tokens, stored hashed, in
HttpOnly; Secure; SameSite=Laxcookies. Signing out and bans end them. - Every state-changing portal request needs a CSRF header and a matching origin; every input is validated; strict security headers and a Content-Security-Policy are set.
- Rate limits per IP on every route, and 60 checks per minute per licence key.
- Blacklists for IPs and hardware IDs are checked before any licence logic.
- Every staff action — including revealing a key — is written to an audit log with who did it and from where.
- AnziLock runs as a non-root container behind a Cloudflare Tunnel, with no open inbound port.
What a Plugin Sends
Each check sends exactly this, and nothing about your players or worlds:
| Field | What it is |
|---|---|
key | Your licence key |
product | Which plugin is asking |
hwid | A one-way hash identifying the server (see Server limit) |
nonce | A random value for this request |
version | The plugin version, used for the update notice |
serverName, port | So you can tell your servers apart on the licence page |
players | The online player count |
AnziLock also sees the public IP the request comes from, which is what the IP limit counts. Request logs are kept for a limited number of days and then pruned. What AnziLock stores, for how long, and how to have it deleted is set out in the Privacy Policy; for questions about your data, open a ticket.
Troubleshooting
| Symptom | Check |
|---|---|
| The plugin disables itself on start | The console block names the code — see Result codes. |
IP_LIMIT_REACHED after moving host | /reset-ips, or Reset IPs on the licence in the portal. If the cooldown is still running, open a ticket. |
Could not reach the license server … no cached verification | The first start needs outgoing HTTPS to license.ajdnikov-dev.com. Some hosts block outgoing connections by default; ask them to allow it. |
Response failed verification | Something between your server and AnziLock altered the answer: a proxy, a filtering firewall, or tampering. The plugin refuses to trust it. |
| My claim found no match | Your BuiltByBit username must match the one on the purchase exactly (case doesn't matter). Set it under Account, or answer staff in the ticket. |
| I didn't get my roles | Run /verify with your key, or check that the licence shows as active in /licenses. |
| My network isn't under Networks | You are signed in with a different Discord account than the licence owner, or the hub runs a version older than 1.2. |
Need more help?
Open a ticket in the Discord server or from Support in the portal.