Build vs Buy: Should You Build Your Own Software Licensing System?
"I'll just generate a random key and check it against a database" is where almost every DIY licensing system starts. It's also usually where the estimate stops — the actual list of things a production license server needs to get right is much longer, and several of the items on it are the kind of bug you don't discover until a customer hits it in production. We're biased (we sell a licensing API), so take the recommendation with a grain of salt — but the list below is real, not a sales pitch.
What "just a database check" misses
A license key that validates against a database row is maybe an afternoon of work. A license system a paying customer can actually rely on needs a lot more underneath it:
Race conditions on activation. Two devices activating the same near-full-seat license at the same instant is a real event, not a hypothetical — without a database-level lock, both can succeed and oversell a seat. A naive SELECT count, UPDATE if count < max has a race window between the read and the write; fixing it correctly means an atomic conditional update or a transaction-scoped lock, and testing it under genuine concurrent load, not just sequentially in a unit test.
Offline verification. If your app needs to keep working without a network call — an air-gapped terminal, a laptop that's offline more than online — a database check can't help you. That needs cryptographically signed tokens (ECDSA or similar), a defined expiry/grace window, and an honest answer for what happens when the device's own clock can't be trusted (nothing fully solves this — see our offline validation deep dive for the real tradeoffs).
Tenant isolation, if you ever sell to more than one customer's customers. A single missing WHERE TenantId = ... clause in one query is a cross-tenant data leak. Defense in depth here means enforcing isolation at the database layer itself (row-level security), not just trusting every application-level query to remember the filter — and that's before you've thought about what an anonymous, pre-authentication request (a customer validating a key before you know who they are) is even allowed to touch.
Abuse detection. A single valid key shared across a hundred machines looks identical to a hundred separate successful validations — each individual request is legitimate. Catching the pattern needs per-key request-volume and distinct-caller-IP tracking over a rolling window, not just a per-IP rate limit (which catches a different, unrelated problem).
Key storage that survives a breach. If your database is ever stolen, what can an attacker do with it? Plaintext keys are the obvious failure mode, but even a straight hash lookup means an attacker with the DB dump can brute-force short/predictable keys offline. Getting this right end-to-end (client-side encryption before a key ever reaches your server, a keyed HMAC for server-side lookup) is a deliberate design decision made early, not a patch applied later.
What building this actually took, concretely
We don't have to guess at the cost — we built exactly this system. A few real numbers from doing it: the activation-race fix needed a Postgres advisory lock plus an atomic UPDATE ... WHERE CurrentActivations < MaxActivations, tested with genuinely concurrent callers hammering a real database, not mocks. Row-level security ended up covering 28 tenant-scoped tables, not just the obvious ones — several anonymous, pre-authentication endpoints (license validation, login, password reset) needed their own careful bootstrap handling to work correctly under a database role that denies access by default. A rate-limiter bug once sat in production for weeks, silently no-op'ing every request, before a routine test caught that the framework's synchronous fast-path was bypassing the actual check entirely.
None of that is exotic engineering — it's the unglamorous, easy-to-get-subtly-wrong work that a licensing system specifically needs, and it took real engineering time to find and fix, some of it only found once real traffic hit the system.
When building your own genuinely makes sense
To be fair to the "build it" side: if your licensing needs are genuinely simple (one product, no floating seats, no offline requirement, no multi-tenant concerns) and you're not planning to sell to other businesses who'll ask about your security model, a basic key-check system might really be enough — and you'll know your own code completely, with no vendor dependency at all. The calculation changes once you need more than one of: floating/concurrent seats, offline support, abuse resistance, or a real audit trail for compliance conversations.
The honest tradeoff
Buying isn't "less engineering," it's engineering someone else already did and keeps maintaining — the real tradeoff is a recurring cost versus a large, ongoing internal maintenance burden, plus the actual quality bar you're willing to hold your own implementation to. If you're evaluating options, PermitCore's Free tier covers a real production use case with no time limit, so you can compare the two paths directly rather than in the abstract.