Skip to main content
Migrate Already selling? Move your customers to Keylight without re-issuing a single key.
Keylight
Blog
keylight licensing paddle

Why I Built My Own Licensing SDK Instead of Using Paddle

4 min read Nicolas Demanez — Founder

A short founder note on why Keylight exists. Every product starts as somebody’s unsolved problem; this is mine, and if you are shipping a paid app you have probably run into the same one.

The problem I kept hitting

I wanted to sell a desktop app directly. Not through the App Store — directly, to customers I could actually talk to. The payment side was easy: Stripe is excellent and the decision took an afternoon.

Then I got to licensing, and everything slowed down.

Stripe takes the money. It does not give you a license key. It does not sign anything your app can verify. It does not know what a device activation is. The moment a customer has paid, you are on your own: you need to mint a key, sign it so it cannot be forged, deliver it, let the app check it, track devices, and revoke it on a refund. None of that is payment processing, so none of it is in Stripe.

So I looked at the platforms that do bundle licensing.

Why the merchant-of-record platforms did not fit

Paddle, Gumroad, and Lemon Squeezy all advertise license keys. I looked hard at each, and the same three problems came up.

The fee. As merchants of record they charge around 5%, against Stripe’s ~2.9%. On every sale, forever. Reasonable if it solved my problem well — but it did not.

Offline validation. This was the dealbreaker. Their licensing is built around an online validation API: to check a key, the app calls the platform’s server. My app is a desktop app, and desktop apps run on planes, behind firewalls, and offline. An online-only check leaves no good option. Fail closed — refuse to run without a server response — and a paying customer who is simply offline cannot use what they bought. Fail open — keep running when the server is unreachable — and the check is trivially bypassed: block the app’s network access and it can never re-check the license or learn it was revoked. The app never actually verifies anything itself; it only knows what the server last told it. I wanted keys the app could verify locally and cryptographically — confirm a license is genuine with no server call, while revocation still lands on a periodic re-check. That is not what these platforms are built for.

Lock-in. Their license keys are tied to their platform. The customer relationship is tied to their platform. If I ever wanted to leave, I would be re-issuing every license and renegotiating my own customer data. I did not want to start a multi-year product on a foundation I could not walk away from.

None of these platforms is bad. They are just built for selling digital goods broadly, not for licensing a desktop app that has to work offline and outlive any one vendor.

What I actually wanted

Stripped down, my requirements were small:

  • Keep my own Stripe account — my customers, my data, my standard rate.
  • License keys that are cryptographically signed, so they cannot be forged or edited.
  • Offline verification — the app checks the key locally, no network call to launch.
  • Device activation limits, so one key is not a master key.
  • Automatic revocation when Stripe processes a refund.
  • An integration small enough to add in an afternoon, not a sprint.

That list is not exotic. It is just the licensing layer — the piece that belongs between Stripe and the app, that nobody was selling as its own thing.

So I built the layer

Keylight is that layer. It connects to your own Stripe account. When a payment completes, it mints an Ed25519-signed license lease and delivers it. Your app ships with a public key and verifies the lease locally — offline, instantly. Refunds revoke the license automatically. Device limits are enforced per machine. The whole client side is a manager and a state switch:

import KeylightSDK

let licensing = try! Keylight.manager(
    sdkKey: "sdk_live_...",
    tenantId: "acme",
    productId: "myapp",
    keyPrefix: "ACME",
    trustedPublicKeyBase64: "<your-public-key>",
    trialDurationDays: 14,
    branding: .init(appName: "My App", purchaseURL: URL(string: "https://acme.example.com/buy")!, supportEmail: "[email protected]", tintColor: .blue)
)

await licensing.checkOnLaunch()

switch licensing.state {
case .licensed:     enablePaidFeatures()
case .trial(let d): showTrialBanner(daysLeft: d)
case .expired:      showRenewalPrompt()
case .invalid:      showActivationSheet()
}

The positioning is deliberate and narrow: use Stripe for payments, use Keylight for licensing. Keylight is not a Stripe competitor and not a merchant of record. It is the missing layer — the one I went looking for, did not find, and decided to build.

If you are weighing the same decision, the Paddle alternative page lays out the comparison in full. And if your licensing story took a different turn than mine, I would genuinely like to hear it — send us your feedback.

Frequently asked

Why build a licensing SDK instead of using Paddle?+

Merchant-of-record platforms bundle licensing into their fee, but their license handling is platform-tied and weak on offline validation. A dedicated layer on Stripe gives lower fees, customer ownership, and proper offline keys.

What is wrong with licensing from Gumroad or Lemon Squeezy?+

Their license keys are tied to the platform and built around an online validation API. For a desktop app that must launch offline, that is the wrong model, and the keys are not portable if you leave.

What does Keylight do differently?+

Keylight sits on top of your own Stripe account and issues Ed25519-signed license leases your app verifies offline, with device activation limits and automatic revocation on refund.

Ready to ship?

Create your account and start licensing your apps in under a minute. Free forever tier included.

Start Free