DEV Community

Cover image for License keys explained: how they actually work (a beginner's guide)
Kasun Hapangama
Kasun Hapangama

Posted on Edited on

License keys explained: how they actually work (a beginner's guide)

Selling a plugin, a bot, a script or a desktop app, and not sure how license keys actually work? You're not alone. Most guides jump straight to code, and skip the part where you decide what a key should do.

This guide explains the whole system in plain words: what a key is, how it gets tied to a computer, how your app checks it (online and offline), how trials and expiry work, and what happens when you revoke one. It applies to any language and any store. At the end there's a short code example and the mistakes that hurt paying customers most.

What a license key actually is

A license key like K7QX-M2PD-9HWT-R4CN is just a random, hard-to-guess ID. On its own it means nothing. It points to a record on a license server, and that record is the truth:

  • who bought it (the customer),
  • what they bought (the product and its rules),
  • its status: active, suspended or revoked,
  • when it expires, if ever,
  • which machines it's activated on.

That's the key difference from the old "serial number" approach, where a key was valid if it passed a math check inside the app. Those get cracked by keygens: once someone reverse-engineers the formula, they can print infinite valid keys. A random key backed by a server can't be generated, only shared, and sharing is something the server can see and stop.

The life of a license key: issued at checkout, activated on a machine, checked on every start, then it expires or is revoked

The four pieces every licensing system has

Piece What it is Example
Product The thing you sell "MyPlugin", "Acme Editor"
Policy The rules one purchase gets 2 machines, 365 days, subscription
License One buyer's key, issued from a policy K7QX-…, active, expires 6 Nov 2027
Machine One computer or server the license runs on a server id, a desktop's machine id

The policy is where most of your decisions live. Change a policy's machine limit and every license on it follows, so you set the rules once instead of per buyer.

A policy in the dashboard: type, max machines, max IPs and duration

Activation and machine limits

A key that works anywhere gets shared. So your app sends two things with every check: the key, and a machine id, a stable ID for the computer or server it runs on (a random ID saved in the app's folder works well; hardware-based IDs change when someone swaps a disk).

The first time a new machine checks in, it activates: it takes one of the license's slots. With "2 machines", a buyer's laptop and desktop both work. A third computer is refused with a clear "too many machines" answer, and the buyer can free a slot (from a customer portal, a Discord bot, or by asking you) when they move to a new PC.

Game-server plugins often use an IP limit instead or as well: the key works from a limited number of network addresses, the classic "one server per purchase" rule.

Be generous. People reinstall, upgrade and move. A support ticket costs you more than one extra slot.

Checking a key: online first

Most apps check online: on startup, the app asks the license server "is this key valid on this machine?" and gets one of a handful of answers.

Answer Means What your app should do
Valid Active, not expired, machine allowed Start normally
Expired Past its end date Offer a renewal
Revoked / Suspended You switched it off Stop, show a support contact
Too many machines The slots are full Ask the buyer to free one
Not found No such key Ask them to check for typos
No answer Network down, server error, timeout Keep running, try again later

That last row is the one beginners get wrong. If your app stops whenever the license server can't be reached, every outage (yours, the buyer's ISP, a firewall) turns paying customers into angry ones. Some marketplaces, BuiltByBit for example, warn that a plugin which disables itself when a server is unreachable may be treated as malicious. Only stop on a clear "no".

Re-check every few hours while the app runs, too. A startup-only check never notices a key you revoked yesterday on a server that hasn't restarted in a month.

Here's the logic in JavaScript (every language looks the same):

import { SunLicense } from "sunlicense";

const client = new SunLicense({ baseUrl: "https://api.sunlicense.com", token: API_KEY });

async function checkLicense() {
  try {
    const result = await client.validate(licenseKey, { host: machineId });
    if (result.valid) return "run";
    if (result.status === 400) return "stop";   // a clear "no": revoked, expired, too many machines...
    return "run";                               // a server error is not a "no"
  } catch {
    return "run";                               // unreachable: keep running, check again later
  }
}
Enter fullscreen mode Exit fullscreen mode

Offline license files

Some customers run with no internet at all. For them, a license server can export a signed license file: the license's details (key, expiry, machine limit), plus a signature made with your private key, usually Ed25519. Your app ships the matching public key and verifies the file without a network. Anyone can read the file, but changing a single character breaks the signature.

Online check or offline file: online revokes fast, the signed file works with no network

The trade-off is revocation: a file someone already has stays valid until it expires. So give files a short life (a month, say) and treat them as a fallback: check online first, and use the file only when the server can't be reached.

Trials, subscriptions and expiry

The policy's type decides how time works:

  • Perpetual: pay once, works forever (usually with updates for a while).
  • Subscription: valid until a date; renewing pushes the date forward. An expired key gets an "expired" answer, so your app can show "renew here" instead of a generic error.
  • Trial: a short duration (7 or 14 days) and often one machine. When it ends, the same "expired" answer becomes your upgrade prompt.

Revoking, suspending and refunds

  • Suspend is a pause: the key stops working until you reinstate it. Use it while you look into something.
  • Revoke is permanent: for a leaked key, or a buyer who charged back.
  • Refunds should revoke automatically. If your store sends refund webhooks (Tebex, Stripe, Lemon Squeezy and others do), connect them, or refunded buyers keep a free copy.

Because every buyer has their own key, revoking one never touches anyone else.

The licenses list: one masked key per buyer, each with its own status

Where keys come from

Don't email keys by hand. Connect your store so a key is created at checkout and delivered to the buyer: by email, on a "redeem" page, or written straight into the download (BuiltByBit and Voxel Shop can do that for Minecraft plugins).

What license keys don't do

A key check is one if in your code, and anyone with a decompiler can delete it. Keys stop casual sharing and make leaks traceable; they don't stop a determined cracker. If that worries you, the next layers are obfuscation and sealing part of your code so it only runs with a valid license. I wrote those up in How to protect your plugin or app from piracy, step by step.

Build it or use a service

You can build all of this: a small API, a database of licenses, a signing key for offline files, and a dashboard to revoke from. Plenty of people do. If you'd rather not, I build SunLicense, which does the pieces above: policies with machine and IP limits, SDKs for Java, JavaScript, Python, .NET, PHP and Lua, signed offline files, store integrations that issue and revoke keys, and a Discord bot for buyers. The free plan covers 3 products and 100 licenses, with no credit card and no cut of your sales.

Writing a Minecraft plugin? The step-by-step version is How to add license keys to a Minecraft plugin in 10 minutes. For anything else, see how SunLicense works and set up your first product.


This article was written with the help of an AI assistant and reviewed by me before publishing.

Top comments (0)