Published
- 10 min read
Selling Out: Engineering a Custom Paywall
The Open Source Ethos
I have always been an open-source purist at heart. If you scroll far enough back through my GitHub, you’ll find a graveyard of projects—half-finished tools, ambitious libraries, weekend experiments—that I built, open-sourced, and quietly abandoned when the next shiny thing caught my eye. I believe, genuinely and perhaps naively, that software should be free, accessible, and easily modifiable. I have a “Free as in Freedom” sticker on my laptop. I once got into a heated argument at a dinner party about the GPL. I am, by all objective measures, insufferable about this.
But there is a harsh, cold reality that is beginning to dawn on me as I prepare to launch a cloud-backed mobile application that runs on… well, everyone else’s hardware. And by “everyone else,” I mean the hardware that belongs to a man named Jeff Bezos, who is not known for his charitable spirit.
The Cost of “Free”
Local software runs on the user’s machine. The marginal cost of someone downloading and using my window manager config, my Neovim plugin, or my terminal prompt is exactly zero. The electricity comes from their wall. The CPU cycles come from their silicon. I write the code, I push it to GitHub, and the universe handles distribution. It is a beautiful, elegant, zero-cost arrangement.
Cloud software is a fundamentally different beast. Every API call that Prior will make—every prediction fetched, every score submitted, every leaderboard refreshed—will hit a server that I am paying for. The database that stores user sessions is hosted on AWS, and Amazon charges me for every read, every write, and probably for looking at the dashboard too hard. The compute required for the predictive modeling backend isn’t trivial; it involves statistical calculations that make the CPU sweat and the billing page weep.
I’ve been running Prior in development with just myself and a few testers, and even that modest usage has the AWS bill creeping upward with the gentle inevitability of entropy. I can already see where this is going. If the app gains any traction at all—even a few hundred users—the infrastructure costs will blow past what I can absorb as a personal hobby expense. “I’ll optimize the queries,” I keep telling myself. I have not optimized the queries.
The Crossroads
I can already see the fork in the road, and I’ve been thinking about it far earlier than I probably need to. But I’d rather confront this now, while the code is still malleable, than scramble to bolt on monetization after launch when users are already accustomed to getting everything for free.
I have three options, each more unpleasant than the last.
Option one: Shut it down before it starts. Never launch. Keep it as a personal tool, archive the repo, move on. This is the noble, pure choice—the digital equivalent of a monk renouncing worldly possessions. But I don’t want to do that. I’ve put months into this. I think it could help people. Killing it before it even breathes feels like giving up on a child you haven’t met yet.
Option two: Ads. I could plaster the app with banner ads, interstitials, and those reward videos where you watch thirty seconds of a mobile game you’ll never download in exchange for virtual currency. This option makes me physically nauseous. Prior is an app designed for focus and deliberate practice. The entire point is that you enter a flow state, make predictions, calibrate your thinking. Interrupting that with “DOWNLOAD RAID: SHADOW LEGENDS” is not just a bad user experience; it is an act of violence against the product’s entire reason for existing. Also, ad revenue for a niche app is approximately enough to buy a single coffee per month. A bad coffee. From a gas station.
Option three: A subscription. Charge the people who use my software to… use my software. The thing I said I’d never do. The thing that 13-year-old me would have written a scathing blog post about (oh, the irony).
I’m going with option three. I am officially going to sell out.
Engineering the Gate
Building a paywall is technically fascinating because it is fundamentally an exercise in adversarial engineering. You are designing a system specifically intended to prevent users from doing what they want to do, unless they give you money. You are, in essence, constructing a very polite hostage situation. “Nice predictions you’ve got there. Shame if you couldn’t… make any more of them.”
The easy way out is to use a service like RevenueCat, slap a hard paywall on the main screen, and call it a day. “Pay $4.99/month or go away.” But I don’t want that. I don’t want to completely lock out users who can’t pay, or who are still deciding whether Prior is worth paying for. The plan is a freemium model—specifically, allowing three free continuous-mode predictions per day. Enough to get value, not enough to satisfy a power user.
This sounds simple. if (dailyUsage < 3) allow() else block(). A first-year CS student could write it. I could write it in my sleep. But the implementation details, as always, are where the devil throws a house party and invites all his friends.
The Storage Problem
Where do you store dailyUsage?
If you store it locally, the user can uninstall and reinstall the app, clearing the local storage, and get infinite free questions forever. Or they can go into their phone’s date settings and manually change the system clock to tomorrow. Or next year. Or the year 3000. The local device has no authority over what “today” means if the user decides to lie about it.
If you store it server-side, every single prediction request now requires a synchronous network round-trip to validate the user’s quota before the question even loads. This would completely ruin the offline-first architecture I’ve been building (see my previous post, in which I describe in excruciating detail why offline-first is simultaneously the best and worst architectural decision I’ve ever made). A user on an airplane wouldn’t even be able to check their remaining quota, let alone use one.
This is the fundamental tension of freemium on mobile: security demands server authority, but UX demands local autonomy. You cannot have both. You pick a side and accept the consequences, or you build some horrifying hybrid that makes everyone moderately unhappy.
The Compromise
I’m going with the hybrid. Of course I am. Making everyone moderately unhappy is the essence of engineering.
The device will track usage locally using a date-keyed counter in persistent storage. When the user is online, the server will validate and reconcile the count during the normal sync process. When the user is offline, the app trusts the local count—because the alternative is telling an offline user “Sorry, I can’t verify your quota, so you can’t use the app,” which is a user experience so bad it borders on satire.
If a user changes their system clock? Yes, they’ll bypass the limit. If they wipe their app data? Yes, the counter resets. If they are sufficiently determined and technically creative, they’ll be able to get free access forever.
I’ve decided not to care.
This is a difficult decision for the engineer in me, who wants an airtight system. But the pragmatist in me has done some back-of-envelope math. The number of users who will be (a) technically savvy enough to manipulate system clocks, (b) motivated enough to do so repeatedly, and (c) unwilling to pay $4.99/month, is going to be vanishingly small. The engineering effort required to build an airtight DRM system—server-side quota checks, JWT-verified timestamps, device fingerprinting, all the apparatus of digital paranoia—would cost me weeks of development time to prevent maybe $20/year in lost revenue. The ROI is negative. Spectacularly negative.
If a user is so desperate to use Prior that they’re willing to uninstall and reinstall the app every three questions, or constantly fiddle with their system time, or whatever other creative workaround they devise—honestly, they’ll have earned the free access. I’ll respect the hustle.
The UX of Denial
The part I’m dreading most isn’t the code. It’s the design. Specifically: how do you design a screen that tells the user “no” without making them want to throw their phone?
I’ve already been through several iterations of the limit screen mockup. My first draft was terrible. It had a red icon, bold text that said “LIMIT REACHED,” and a button that said “UPGRADE NOW.” It looked like an error page at a bank. It felt like a mugging. I hated it.
The version I’m settling on is gentler. The messaging is something like, “You’ve completed your free predictions for today. If you’re getting value out of Prior, consider supporting development.” No exclamation points. No urgency. No “LIMITED TIME OFFER” manipulation. Just a calm, honest statement. Here is what happened. Here is what you can do about it. No pressure.
It’s a delicate balance and I’m not sure I’ll get it right on the first try. I want the friction to be noticeable enough to drive conversions—the whole point is that some people will hit this screen and think, “Yeah, this is worth $5”—but not so abrasive that the user uninstalls the app in frustration and leaves a one-star review that just says “greedy.” The line between “gentle nudge” and “aggressive upsell” is thin, subjective, and impossible to A/B test when your projected user base fits in a mid-size sedan.
Making Peace (Preemptively)
The commit that introduces the paywall is going to feel bad. I already know it. I can feel the guilt accumulating in advance, like emotional credit card debt. I’ll sit there staring at the git diff, this clean little wall of code whose sole purpose is to say “no” to people who want to use something I made. It’s going to feel like putting a lock on a public park.
Submitting the in-app purchase to the Apple App Store is going to feel worse. There’s a configuration screen where you type in the subscription price, the billing period, and the promotional text. It has a field for “Subscription Description” where you explain, in 45 characters or fewer, what the user gets. I’ll type “Unlimited daily predictions” and stare at it. I’ll be selling access to my own math. Math that I wrote in my bedroom at 1 AM. Math that, in my heart, I believe should be free.
But I keep thinking about what it will feel like when the first subscription comes through. If it ever does. I don’t think it will be greed—I genuinely don’t care about the money (which is good, because the amount is not going to be life-changing). I think it will be validation. Somewhere out there, a stranger—a person I have never met and will probably never meet—will have found enough value in this weird little app I built that they were willing to part with their hard-earned money to keep using it. They’ll look at the paywall, consider the value proposition, and say, “Yeah, this is worth it.”
I think that will feel good. Really good. Maybe better than any open-source star or GitHub fork ever has.
Software wants to be free, but servers want to be paid, and developers want to eat. And until we figure out a better economic model for the internet—one that doesn’t involve either advertising surveillance or subscription fatigue—I’ll be over here, carefully tuning my free-tier logic and trying not to feel too guilty about it before I’ve even shipped the thing.