Every developer shipping paid software hits the same fork in the road: spin up your own license key generator, or plug into someone else's API. Here's an honest look at both paths β including working code you can run today.
You've finished the software. The installer builds clean, the tests pass, and you're one step away from charging money for it. Then comes the question every indie developer and small team faces: how do people actually unlock this thing?
The internet gives you two confident, contradictory answers. One camp says licensing is trivially easy β "it's just a random string in a database, you'll build it in an afternoon." The other camp says you'd be reckless to try β "licensing is a solved problem, don't reinvent the wheel." Both camps are partly right, which is exactly why this decision trips people up.
This article is our attempt to give you the balanced version. We'll break down what a license key generator really involves, walk through building a minimal-but-real one in Node.js, show you what a managed software license key API gives you instead, compare the options side by side, and finish with a flowchart so you can decide based on your product β not on someone's affiliate link. And yes, we make money when you pick our service, so we've tried extra hard to tell you when not to.
"Generate a license key" sounds like one job. It's actually four jobs wearing a trench coat:
Here's the trap: generation is 5% of the work and 100% of the perceived difficulty. Most developers who "roll their own licensing" budget their time for generation and are surprised by the other 95%. Keep that ratio in mind as we evaluate both options.
The DIY approach means you own the whole pipeline: a function that mints keys, storage that tracks them, an endpoint your app calls for license key validation, and whatever admin tooling you need to manage it all. There is nothing magical about it β it's ordinary backend work, and plenty of products run happily this way.
If you go this route, do it deliberately β budget weeks, not afternoons, and follow the tutorial in section 6 rather than reaching for Math.random().
A managed licensing service flips the model: you keep a dashboard tab open instead of a codebase. Generation, validation, machine limits, trials, revocation, analytics β the provider runs those as a product, exposes them over HTTPS, and you integrate with an HTTP call or a small SDK.
In the build vs buy license system decision, buying gets you:
A service is a dependency and a bill. If the provider raises prices, changes their API, or disappears, migration pain is real. Vendor lock-in is genuine β though a good integration wraps the licensing calls behind one module of your own, which caps the blast radius. And at very large scale, per-validation pricing can eventually exceed the cost of a well-run in-house system. "Eventually" is doing heavy lifting there; most products never reach it.
Here's how the main paths stack up as of August 2026. Competitor capabilities and pricing shift over time, so treat third-party entries as directional β check current plans before committing.
| Feature | DIY | UnifiedLicensing | Keygen | Gumroad |
|---|---|---|---|---|
| Key generation | You build it | Built-in | Built-in | Built-in |
| Validation API | You build it | REST API | REST API | Basic verify endpoint |
| SDKs / integration helpers | None | JS, .NET, PHP, Python, mobile | Multiple languages | Community libs only |
| Offline support | Roll your own | Cached grace periods | Signed/cryptographic | No |
| Machine locking | You build it | Built-in, per-device | Built-in | No |
| Trials | You build it | Device-scoped trials API | Supported | Limited |
| Analytics & activation insights | You build it | Dashboard included | Dashboard included | Sales data only |
| Cost | $0/mo + your time | Free tier; paid plans scale up | Free tier; cloud plans paid | Free + % fee per sale |
| Maintenance burden | 100% yours | Provider's | Provider's | Provider's |
| Security responsibility | 100% yours | Shared (provider handles API) | Shared | Shared |
Read that table as a spectrum. Gumroad-style storefront licensing is really "payment platform with a bolt-on key check" β fine for ebooks and simple downloads. Keygen sits at the heavyweight end with cryptographic offline licensing. UnifiedLicensing aims at the pragmatic middle: a full software license key API that a solo developer can integrate in under an hour, starting free.
Enough theory. Here is a genuinely working license key generator in Node.js β no dependencies beyond the standard library. It produces classic XXXXX-XXXXX-XXXXX-style keys whose authenticity can be verified offline using an HMAC-SHA256 signature.
// keygen.js β minimal license key generator (Node.js, stdlib only)
const crypto = require('crypto');
// Load the signing secret from the environment. NEVER hardcode it.
const SECRET = process.env.LICENSE_SECRET;
if (!SECRET || SECRET.length < 32) {
throw new Error('Set LICENSE_SECRET (32+ chars) before generating keys');
}
function generateKey() {
// 1. Entropy β 80 bits of cryptographically secure randomness.
// crypto.randomBytes, NOT Math.random(), which is predictable.
const entropy = crypto.randomBytes(10).toString('hex').toUpperCase();
// 2. Signature β HMAC proves this key was minted by *us*,
// so offline verifiers can reject forgeries without a DB.
const sig = crypto
.createHmac('sha256', SECRET)
.update(entropy)
.digest('hex')
.slice(0, 10) // 40-bit truncated signature
.toUpperCase();
// 3. Formatting β chunk into 5-char blocks humans can read aloud
// over the phone without confusing 0/O or 1/I (hex helps here).
const chunk = (s) => s.match(/.{1,5}/g).join('-');
return `${chunk(entropy)}-${chunk(sig)}`;
}
function isValidKey(key, expectedEntropySet = null) {
const compact = String(key).toUpperCase().replace(/-/g, '');
// Structural check: 30 hex chars total (20 entropy + 10 sig).
if (!/^[0-9A-F]{30}$/.test(compact)) return false;
const entropy = compact.slice(0, 20);
const givenSig = compact.slice(20);
const expectedSig = crypto
.createHmac('sha256', SECRET)
.update(entropy)
.digest('hex')
.slice(0, 10)
.toUpperCase();
// Constant-time compare β avoids leaking the answer via
// millisecond-level timing differences.
return crypto.timingSafeEqual(
Buffer.from(givenSig),
Buffer.from(expectedSig)
) && (!expectedEntropySet || expectedEntropySet.has(entropy));
}
module.exports = { generateKey, isValidKey };
Using it is straightforward:
const { generateKey, isValidKey } = require('./keygen');
const key = generateKey();
console.log(key); // e.g. 7F3A2-B91C4-D08E5-A62F7-1B9D3-E4C58
console.log(isValidKey(key)); // true
console.log(isValidKey(key.slice(0, -1) + '0')); // false β bad signature
Let's walk through the deliberate choices, because each one is a lesson:
crypto.randomBytes, never Math.random(). Predictable keys get brute-forced. Every year someone's SaaS gets popped because their "random" tokens came from a seeded PRNG.timingSafeEqual instead of ===. Naive string comparison leaks information through response timing. It's a one-line fix; most hand-rolled validators skip it.0/O and 1/l confusion of base32/base64 and survives aggressive copy-paste mangling.This generator proves a key is authentic. It knows nothing else. There's no record of who owns the key, no expiry date, no way to revoke a key that leaked on a forum, no machine locking, no upgrade path from Basic to Pro, and no analytics. To get those, you now need a database, an admin UI, a validation endpoint with rate limiting, backup proceduresβ¦ which is precisely the 95% we mentioned earlier. Notice the optional expectedEntropySet parameter above β even basic revocation requires you to start tracking issued keys somewhere.
We promised balance, so here it is stated plainly: building your own is sometimes the right call. Consider going DIY when most of these describe you:
If three or more of those bullets fit, build. Honestly β the worst outcome is paying a service fee for capabilities you neither needed nor used.
Flip side: adopt a managed software license key API when these sound like your situation:
Most indie software β plugins, utilities, games, desktop tools, premium themes β lands squarely in this list. If you're nodding along to two or three bullets, a free-tier service will likely carry you further than any weekend-built system.
To make the "buy" option concrete, here's the entire integration surface: one HTTPS call. This is the actual request our SDKs wrap β plain REST, no proprietary protocol.
POST https://api.unifiedlicensing.com/api/v1/validate-license
Content-Type: application/json
{
"api_key": "ul_YOUR_API_KEY", // from your vendor settings
"product_key": "YOUR_PRODUCT_KEY", // identifies this product
"license_key": "7F3A2-B91C4-D08E5-A62F7",
"machine_id": "web-a1b2c3d4", // enables machine locking
"platform": "web"
}
And the response for a healthy license:
{
"valid": true,
"license_key": "7F3A2-B91C4-D08E5-A62F7",
"status": "active",
"plan": "pro",
"expires_at": "2027-01-15T00:00:00Z",
"features": ["dark_mode", "export_pdf"],
"machine_id": "web-a1b2c3d4",
"validated_at": "2026-08-21T14:30:00Z"
}
Note what you get for free in that payload: plan tiering (plan), expiry handling, per-feature gating (features), and device binding (machine_id). Invalid cases come back structured too β expired, revoked, wrong product, seat limit exceeded β so your app can show precise messages instead of a generic "no." The same API family covers trials (/start-trial, /check-trial) and heartbeats, with automatic offline caching and grace periods handled by our platform SDKs.
Total integration effort: typically under an hour, and there's a free tier so you can validate the whole flow before spending anything. Platform-specific walkthroughs live in our integration guides β JavaScript, .NET, Python, PHP, WordPress, Chrome extensions, HTML5 games, and mobile.
Answer the questions top to bottom; stop when you hit a verdict.
START: I need to license my software
β
ββ Is selling/licensing software itself your core product?
β ββ YES βββΊ BUILD. The domain is your business.
β
ββ Hard requirement for air-gapped / exotic platforms /
β on-prem data residency?
β ββ YES βββΊ BUILD (or find a specialist vendor first).
β
ββ Solo dev or team β€ 5, shipping within a month?
β ββ YES βββΊ USE A SERVICE. Start on a free tier.
β
ββ Will your software be publicly distributed
β (desktop app, plugin, game, template)?
β ββ YES βββΊ USE A SERVICE.
β Machine locking + revocation alone justify it.
β
ββ Existing 24/7 backend team + very high validation volume?
β ββ YES βββΊ BUILD β marginal cost is low at your scale.
β Revisit annually as volume grows.
β
ββ None of the above cleanly apply?
ββ Default: SERVICE on a free tier.
Wrap the calls behind one module so you can swap later.
Building your own later is always possible;
unwinding a broken DIY system mid-growth is not.
A signature proves a key is authentic, but it can't express state: revoked? expired? seat limit reached? upgraded plan? Any purely offline scheme either accepts leaked keys forever or requires you to ship updated key databases to every client. Server-side license key validation keeps state authoritative in one place; the best-practice hybrid pairs online validation with short-lived cached results for offline use β which is exactly how UnifiedLicensing's grace-period model works.
Yes β sufficiently motivated attackers can patch any client-side check, whether you built it or licensed it. But licensing isn't a wall; it's friction economics. The realistic goal is stopping casual sharing and casual piracy, which is where the overwhelming majority of revenue loss occurs. Server validation with machine locking eliminates the "paste the key in the group chat" failure mode, and that's most of the battle.
Math.random() unsuitable for generating license keys?Math.random() is a seeded pseudo-random generator β observe a few outputs and its internal state can be reconstructed, letting attackers predict future keys. Cryptographic generators like crypto.randomBytes() (Node), secrets (Python), or RandomNumberGenerator (.NET) draw from OS entropy and don't have this weakness. Any key that gates paid access should come from a CSPRNG, full stop.
Honest accounting: expect roughly 2β6 weeks to build a solid v1 (generation, storage, validation API, admin tooling, rate limiting), then several hours per month in maintenance and support indefinitely β plus fraud losses if you skip machine locking. Services trade that for a subscription; UnifiedLicensing's free tier keeps that cost at zero until your product has meaningful volume. Run both numbers annually against your actual hourly value and the winner is usually obvious.
Less than you'd think, if you integrate defensively. Put every licensing call behind one thin module in your codebase (e.g., licensing.validate(key, deviceId)) and the provider becomes a swappable implementation detail. Export your key database periodically as a hedge. Migration between services β or from a service to your own system once you're huge β is then a bounded project, not a rewrite.
Generate keys, validate them anywhere, lock them to machines, run trials, and watch it all from one dashboard β with a REST API your app talks to in a single call.
Start Free with UnifiedLicensing Free tier included Β· No credit card required Β· Integrate in under an hour