All posts

How to set up license keys in a native macOS app

Nicholas Affonso

November 26, 2025

How to set up license keys in a native macOS app

Building a paid macOS app often means you need a way to verify license keys, prevent piracy, and give customers a simple way to manage their access. In this guide, you'll learn how to set up license key validation and activation in a Swift-based macOS app using Keyforge.

We'll also cover how to integrate payments through Stripe, Lemon Squeezy, or Polar and how to let users manage their licenses through the customer portal.

Prerequisites

Before adding licensing to your Mac app, make sure you've completed these steps:

Once done, you'll have a license key you can use to activate your app.

Activating a license in Swift

When a user launches your app for the first time (or when no license key is stored), prompt them to enter their license key. Send it to the Keyforge API to activate the license on that device.

import Foundation

Task {
  let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/activate")!
  var request = URLRequest(url: url)
  request.httpMethod = "POST"
  request.setValue("application/json", forHTTPHeaderField: "Content-Type")

  let body: [String: Any] = [
    "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
    "deviceIdentifier": "macbook-unique-id",
    "deviceName": "John's MacBook Pro",
    "productId": "p_123456"
  ]

  request.httpBody = try! JSONSerialization.data(withJSONObject: body)

  let (data, _) = try! await URLSession.shared.data(for: request)
  let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]

  print("License activated:", json)
}

After activation, securely store the license key in the Keychain so it can be reused for future validations.

Getting a unique device identifier

There are many ways to acquire a consistent device identifier:

  • IOPlatformUUID from the I/O Registry
  • A UUID stored in Keychain on first launch. Make sure the identifier remains stable between app restarts to keep license activations consistent.

Validating a license at startup

Every time the app launches, you should verify that the license is still valid. This ensures it hasn't been revoked or expired.

import Foundation

Task {
  let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/validate")!
  var request = URLRequest(url: url)
  request.httpMethod = "POST"
  request.setValue("application/json", forHTTPHeaderField: "Content-Type")

  let body: [String: Any] = [
    "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
    "deviceIdentifier": "macbook-unique-id",
    "productId": "p_123456"
  ]

  request.httpBody = try! JSONSerialization.data(withJSONObject: body)

  let (data, _) = try! await URLSession.shared.data(for: request)
  let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]

  if let isValid = json["isValid"] as? Bool, isValid {
    print("License is valid ✅")
  } else {
    print("License invalid ❌")
  }
}

This call can be made periodically, such as every time the app opens or every few hours.


Payments and customer portal

Connect a payment provider to automatically generate and deliver a license key by email when someone purchases your app, with no backend or webhooks needed. Keyforge supports Stripe, Lemon Squeezy, and Polar for both one-time payments and subscriptions.

For subscriptions, expiration dates update automatically on each renewal, and the license is deactivated when a subscription is cancelled. Follow the payments setup guide to connect your product. Customers can manage their licenses, devices, and billing through the customer portal.

License upgrades and renewals

You can offer customers the option to upgrade their license to a higher tier or extend the expiration date of a timed license, directly from the customer portal with no code required. Configure up to 3 upgrade or renewal options per product in the dashboard.

Perpetual fallback

Perpetual fallback lets customers continue using your app with limited functionality after their license expires, instead of losing access entirely. When enabled on a product, the validate endpoint returns status: "fallbacked" for expired licenses.

if let status = json["status"] as? String, status == "active" {
  // Full access
} else if let status = json["status"] as? String, status == "fallbacked" {
  // License expired - grant limited access based on your product's logic
  print("Running in limited mode")
} else {
  // Invalid - show the license entry prompt
}

Offline licensing

If your app needs to run without internet access, Keyforge supports offline license tokens. These are signed JWTs issued by Keyforge that your app can verify locally using a public key, with no network call required.

When license tokens are enabled for a product, the activation response includes a token field. Store it securely in the Keychain alongside the license key.

// After activation, extract and store the token
if let token = json["token"] as? String {
  saveToKeychain(key: "license_token", value: token)
}

On each app launch, retrieve the token from Keychain and verify it locally. See the offline licensing guide for the full implementation, including how to verify the JWT signature using the product's public key.


Conclusion

You've now implemented a full licensing system in your Swift macOS app using Keyforge, from activation and validation to payments and customer self-service. This setup helps protect your app while providing a smooth experience for users, whether they're online or offline.

For more details, visit the Keyforge documentation.

Simplify your licensing process

Focus on building your product and let us handle licensing. Manage license keys via payments and offer your customers a smooth self-serve experience.