
Licensing looks simple from the outside, generate a key, check it, let the customer in. In practice, it isn't, it is not the kind of thing you can vibe-code in a few hours and call done. The gap between a system that quietly works and one that bleeds revenue is a handful of design choices, and they are not the ones most teams expect.
Here is the short list, in roughly the order they show up when you actually build one.
Start with a realistic threat model
The realistic threats to most licensing systems are not crackers distributing patched binaries. They are:
- Casual key sharing between friends or coworkers.
- Lapsed users hunting for workarounds when a license expires and the app stops working.
- Tampering with naive client-side checks, usually with a debugger.
- Operational risk: lost keys, locked-out customers, broken activation flows.
Design for those. The goal is to make legitimate use frictionless and unauthorized use uninteresting. The sophisticated cracker was never going to be a paying customer in the first place.
Generate keys on a server
Firstly, never ship a key generator with the app Once that algorithm leaks, every future key is forgeable. The generator is a server-only concern.
Keep metadata on the server, not in the key
A license key can be a random opaque string. Encoding expiration, tier, or product into the key itself is tempting and almost always wrong, because the real version of that data lives on the server and changes after the key is issued.
Bind licenses to devices
A key on its own is trivial to share. Binding activation to a specific device caps how widely the same key can spread before the device limit runs out.
The activation flow
On first launch, send the key and a stable device identifier to your licensing API. The server records the activation, enforces a device cap, and returns whatever the app needs going forward. Every subsequent validation checks both the key and the device.
Pick a sensible device cap
Three to five devices per user works for most paid software. Enough for someone with a phone, laptop, and desktop. Tight enough that sharing gets annoying fast.
Verify locally with signed tokens
Always-online license checks are a poor experience. They break on planes, behind restrictive networks, and on unreliable connections. Once you have a better option, they are also pointless from a security standpoint.
The better option is a cryptographically signed token, issued at activation and verified locally. JSON Web Tokens are the standard: the server signs with a private key, the client verifies with the matching public key, and no shared secret ever lives on the device.
// Verify the signature locally at startup
verify(token, publicKey);
// Then check the claims that matter
assert(payload.exp > now);
assert(payload.product.id === expectedProductId);
assert(payload.device.identifier === currentDeviceId);Keyforge's offline license validation is built on exactly this pattern.
Set expirations that match how the app is used
Tokens have to expire, or revocation never reaches the device.
The right window depends on the product. A week or a month for daily-use software. Longer for tools people pick up and put down. The app refreshes quietly in the background whenever it has connectivity.
When a refresh fails because the user is offline, give them a grace period before restricting access. Cutting someone off at the precise expiry timestamp is one of the most predictable sources of one-star reviews for licensed software.
Plan for revocation from day one
You will eventually need to invalidate a license. Chargebacks, refunds, abuse, compromised accounts. Two things have to be in place:
- A server-side way to mark a license as revoked.
- A token lifetime short enough that revoked clients lose access within a reasonable window.
The second is what teams miss. Year-long tokens mean a revoked license keeps working on every device that already validated, for up to a year.
Keep secrets out of the binary
The application binary is not a safe place to store anything you would not happily publish.
What can ship with the client
Public verification keys. They are designed to be distributed.
What can't
API keys, signing keys, or anything that can issue or modify licenses. None of that goes on the device. If your client needs to hit an authenticated endpoint, route it through your backend.
The Keyforge public API is designed to be called directly from a client for this reason, so no secret has to travel with the binary.
Make the legitimate path easier than the workaround
A hard cutoff at license expiry pushes users toward workarounds, because the workaround is easier than figuring out how to re-purchase from inside a locked app.
Letting the app degrade gracefully, with a renewal banner and a one-click path back, inverts that calculation. The user who was going to look for a crack is now looking at a renewal CTA inside your app, which is exactly where you want them.
This is what perpetual fallback access is for, and it pays back in retention long after the licensing question is settled.
Treat self-service as a security feature
A surprising amount of license abuse is downstream of bad self-service. When customers cannot move a license to a new computer, see which devices are activated, or reset a slot, they share keys instead.
A self-serve portal tied to the customer's email removes that pressure. It also cuts down on support threads that pass license data around in plaintext, which is its own quiet security problem.
Wrapping up
The short version:
- Generate keys on a server.
- Bind them to devices.
- Verify locally with signed tokens.
- Set realistic expirations.
- Plan for revocation.
- Keep secrets off the device.
- Make renewing easier than cheating.
If you are building all of this from scratch, it is more work than it looks. Keyforge implements it by default, so you can ship licensing without owning the infrastructure behind it.
Sell your software product with ease
Focus on building your product and let us handle licensing. Manage license keys via payments and offer your customers a smooth self-serve experience.