You built a desktop app, wired up a subscription, and it works. People download it, pay you monthly, and enter a license key to unlock the full version. Then one day you find your app being passed around a forum, or you notice a hundred active users but only twelve paying ones. Your revenue is leaking, and you have no idea how it happened.
I see this almost every week. A non-technical founder ships a paid desktop app built with an AI tool, feels good about the license check the AI wrote, and never realizes that check can be bypassed by anyone with ten minutes and a text editor. Desktop app license protection is one of the hardest things to get right, and it is exactly the kind of thing AI builders get spectacularly wrong.
In this post I will explain, in plain English, why AI-generated license checks are so easy to crack, how to tell if yours is one of them, and how to add licensing that actually protects your subscription revenue. You do not need to understand the code to follow along.
What "license protection" actually means
A license check is the gate that decides whether someone is allowed to use your paid app. When a user enters a key or logs in, your app is supposed to ask: is this person really a paying customer, and are they still paying?
There are two fundamentally different ways to answer that question, and the difference is everything.
- Local validation: your app checks the license by itself, on the user's computer, using logic baked into the app.
- Server validation: your app asks your server, which you control, whether the license is real and active right now.
Local validation feels simpler and it is what AI tools reach for by default. It is also the reason your app gets pirated. When the check happens on the user's machine, the user owns the machine, which means they own the check. They can inspect it, change it, or rip it out entirely.
Server validation moves the decision somewhere the user cannot touch. That single shift is the line between a license that holds and a license that is decorative.
Why AI-built license checks are trivially bypassed
Here is the uncomfortable truth about desktop apps: the code runs on a stranger's computer. On the web, your important logic lives on a server the user never sees. On the desktop, you are shipping your logic directly into the hands of the person you are trying to charge.
AI builders do not account for this. When you ask an AI to "add a license check," it produces something that works in the demo, which means it works when you, the trusting founder, type in a valid key. It does not think like an attacker, because nothing in its training rewarded it for that. It optimizes for "the feature runs," not "the feature survives someone actively trying to defeat it."
So it writes something like this, right inside your app:
// The kind of check an AI tool loves to generate
function isLicenseValid(key) {
return key === "PRO-2024-UNLOCK";
}
if (isLicenseValid(userKey)) {
unlockAllFeatures();
}
Look at what is wrong here. The correct answer is sitting in plain text inside the app you shipped. Anyone who opens the app's files can read the magic key and share it. Even if the AI makes it fancier with math or hashing, the entire check still happens locally, so a determined user can find the line that says if valid and simply flip it to always say yes.
This is the same root problem I wrote about in why your AI-built auth is probably broken: AI tools confuse "it works when used honestly" with "it is secure." A login screen that trusts the browser and a license check that trusts the desktop are the same mistake wearing different clothes.
A few specific ways AI-generated checks fall apart:
- The secret is in the shipped app. Keys, unlock codes, and validation rules are readable by anyone who unzips your app.
- The check is a single yes/no on the user's machine. Flip the answer and the whole thing opens.
- It only checks once, at startup. Once past the gate, nothing ever re-verifies that the subscription is still active. Someone can cancel and keep using it forever.
- There is no connection to your billing. The app has no idea whether the person actually still pays you.
That last point is the killer for subscription businesses. A one-time app you can afford to lose a little to piracy. A subscription depends on continuously confirming that a paying relationship still exists, and a local check never does that.
What a cracked license looks like from your side
You will rarely catch someone in the act. Instead you notice the aftermath. Here are the symptoms I tell founders to watch for.
Your active users outnumber your paying users
If your analytics show far more people opening the app than your billing dashboard shows paying, that gap is often piracy or shared keys. A little slippage is normal. A two-to-one or five-to-one gap is a leak.
The same license key is active in many places
One key running on forty different computers in five countries is not a power user. It is one key that got shared or posted publicly. If you cannot even see this happening, that is its own red flag, because it means your app is not reporting anything back to you.
Cancelled customers never really leave
You see a subscription get cancelled in Stripe, but the person keeps using the app for months. That means your app never rechecks whether they still pay. The gate opened once and stayed open.
Your app shows up somewhere you did not put it
Cracked versions get shared on download sites and forums. A quick search of your app's name plus words like "free," "crack," or "license" occasionally turns up an unpleasant surprise.
If any of this sounds familiar, you are not doing anything wrong. This is the normal next chapter for a fast-built app, and it is fixable.
A license check that runs on your customer's computer is a suggestion. A license check that runs on your server is a rule.
How to add licensing that actually protects your revenue
The goal is to move the real decision off the user's machine and onto a server you control, then keep checking. You do not have to boil the ocean. Here is the approach I use, in order of impact.
Step 1: Make your server the source of truth
Instead of the app deciding for itself whether a key is valid, the app should ask your server every time it matters. Your server holds the list of who is a real, currently-paying customer, and the app trusts that answer, not its own.
The flow looks like this:
- The user enters their license key or logs in.
- Your app sends that key to your server over an encrypted connection.
- Your server checks it against your real customer records and your billing system.
- Your server replies with a signed yes or no, plus what features to unlock.
The important word is signed. Your server attaches a cryptographic signature to its answer, using a secret key that only your server knows. Your app can verify that signature came from you and was not faked by someone running a lookalike server. This stops the classic crack where someone points your app at a fake server that always says yes.
Step 2: Tie licenses to your billing system, not to a hardcoded list
Your license status should come straight from your payment provider (Stripe, Paddle, Lemon Squeezy, whatever you use). When someone pays, they get access. When they cancel or their card fails, access ends automatically.
This is where a lot of AI-built subscription apps quietly break, and I dug into the specifics in payment integration mistakes in vibe-coded SaaS. If your billing and your licensing are not connected, you are manually reconciling two systems forever, and every gap between them is lost money.
Step 3: Recheck on a schedule, not just at startup
A subscription needs ongoing proof of payment. Have the app re-verify with your server on a sensible rhythm, for example every time it launches and then periodically while it runs. If the subscription lapsed, the app gracefully locks the paid features and asks the user to renew.
Build this to be forgiving of real life. People go offline, planes have no wifi, servers have hiccups. A good design allows a short grace period so a paying customer on a train does not get locked out, while a cancelled account still loses access within a day or two. Getting that balance right matters, because being too aggressive punishes your real customers.
Step 4: Limit how many machines a key can run on
Track which devices a license is active on, on your server. If one key suddenly appears on twenty machines, you can flag it, cap it, or revoke it. This alone shuts down the "one person buys, shares with their whole group" leak, which is the most common form of casual piracy.
Step 5: Accept that you are raising the cost, not building a fortress
Here is the honest part. No desktop app is uncrackable. A truly determined attacker with enough time can defeat almost anything, because the code runs on their machine. That is a law of physics, not a flaw in your app.
But that is not the fight you are actually in. You are not defending against elite crackers. You are defending against ordinary people sharing keys and casual users grabbing a free copy. Server validation raises the effort required from "ten minutes with a text editor" to "genuine reverse-engineering skills most people do not have and will not bother with." That shift recovers the vast majority of your leaked revenue.
What you can do yourself, and where I come in
Some of this you can handle on your own, and you should.
- Search for cracked copies of your app so you know if there is a problem.
- Compare your active users to your paying users and watch that gap.
- Turn on the tools your payment provider offers for tracking cancellations and failed payments.
- Write down how your current license check works so you can explain it to someone.
Where I would be careful is building the server validation itself. This is security-critical code, and the details are exactly where things go wrong: how you sign the responses, how you store your secret key, how you handle the offline grace period, how you connect billing events to access without leaving a gap. Get one of those wrong and you either lock out real customers or leave the same door open you were trying to close.
This is also not a job to hand back to the AI that wrote the broken check in the first place. Asking it to fix its own license logic is asking it to grade its own homework, and it repeats the same blind spot. Fixing security holes with more AI is how a small leak becomes a rebuild, which is the kind of accumulating problem I described in the hidden cost of vibe-coding debt.
If you are selling a desktop app on subscription and you have never been sure whether your license check actually holds, that uncertainty is the real problem, and it is a solvable one. I review AI-built apps every week, find where the license and payment logic is leaking, and either lock it down or hand you a clear plan so you know exactly what you are dealing with. If you would rather stop guessing about how much revenue is quietly walking out the door, let's talk about what your app needs.
Cover photo by Ruben Boekeloo on Pexels.
