Skip to main content
This guide builds a complete sales flow for a small desktop app that should work without the internet after purchase:
  1. A static page has a Stripe Checkout button. The product is a $2.99 lifetime license.
  2. Stripe tells AuthForge Commerce about the payment. Commerce creates a perpetual license with 5 seats and emails the key to the buyer.
  3. The app runs as a 1-hour free trial until the buyer enters a key.
  4. The app calls activate_offline once. AuthForge returns a lifetime .authforge file bound to that machine.
  5. From then on the app starts from the file with no network calls, forever.
You host no server. The only online moment for the buyer is the activation call.
A refund or chargeback revokes the license online but cannot reach files already issued. For a lifetime file, that means the buyer keeps the app. Revoking stops new activations only. There is no remote kill switch. Choose this model only if you accept that trade-off at your price point.

Before you start

You need:
  • An AuthForge app (App ID, App Secret, public key).
  • A Stripe account.
  • One of the six SDKs in your app. The method is activate_offline in Python and Rust, activateOffline in Node.js, and ActivateOffline in Go, C# and C++. See Self-serve activation.
Self-serve activation only works with:
  • Perpetual licenses. Licenses with an expiry are refused with offline_activation_requires_perpetual. Subscriptions and time-limited licenses are not supported.
  • Seat-limited licenses. Unlimited-seat (shared) keys are refused with offline_activation_requires_seats.
  • Apps that opted in. It is off by default. Otherwise the call fails with offline_activation_disabled.

What it costs

A license with 5 seats can cost at most 10 credits over its whole life: 2 credits per machine, once. Most buyers use one or two machines. At the smallest credit tier (10for10,000credits)oneactivationcosts10 for 10,000 credits) one activation costs 0.002. See Managing credits. Activations happen when buyers install, which can be months after the sale. Keep a credit balance or turn on auto-refill. With no credits, new activations fail with no_credits. Machines that already have a file keep working.

1. Turn on self-serve activation

In the dashboard, open the app’s settings and turn on Self-serve offline activation. Or use the Developer API:
The key needs the write:apps scope. See Licenses API -> Self-serve offline activation.

2. Sell the license with Stripe

Create the product

In Stripe, create a product with a one-time price of $2.99. Copy the price ID (price_...).

Connect Commerce and map the price

Follow Commerce setup to connect Stripe. Subscribe the Stripe webhook to checkout.session.completed, and to charge.refunded and charge.dispute.created so refunds and chargebacks revoke the license. Then add a product mapping: The HWID slots number is also the machine cap: the license can activate at most 5 distinct machines, ever. Pick a number that covers a buyer’s laptop, desktop and a couple of hardware changes. Raising it later for one buyer is easy; taking a file back is impossible. Leave Email license key to buyer on (the default, in the app’s portal policy). Commerce emails the key as soon as the payment clears. The email is plain text, so the buyer can copy the key into your app.

Add the button to your page

A static page needs no server. Use a Stripe Payment Link or Stripe’s Buy Button for the same price:
Payment Link purchases arrive as checkout.session.completed without line items, so Commerce needs your Stripe API key to look up the price. The Commerce setup covers this. If you host your own Stripe webhook instead of using Commerce, create the license with expiresAt: null and maxHwidSlots: 5. See Custom Stripe webhooks.

3. Build the app

The app does three things on launch:
  1. If a license file exists and login_from_file accepts it, run. No network, no credits.
  2. Otherwise, if the trial has time left, run in trial mode.
  3. Otherwise, ask for the key and call activate_offline with the file path.
If the file exists but fails (for example hwid_mismatch after new hardware), the app falls through to the key prompt. Activating on the new hardware counts as a new machine.
activate_offline is an online call, so the build needs the App Secret. This is different from air-gapped offline files, which ship without it. The secret cannot create, change or revoke licenses: activating still needs a valid, paid key. See Security best practices.

Full example with the trial timer (Python)

AuthForge does not track trials. The trial below is a local timer: one hour from the first launch, stored in the app’s data folder. A user who deletes that file or winds back the clock gets more trial time. For a $2.99 app that is usually fine. If you prefer one hour of use, store the minutes used instead of the start time.
Also give the user an Enter license key action during the trial that calls activate(), so a buyer does not have to wait for the trial to end.

The activation pattern in every SDK

The core is the same in every language: try the file, otherwise prompt and activate. On offline_activation_limit_reached, tell the user to contact you.
What the SDK does on activate_offline:
  • Sends one request to POST /auth/offline/activate with the key, this machine’s HWID (the same one login() would send) and a fresh nonce.
  • Verifies the returned file locally (signature, app id, this machine’s HWID) before saving it. A file that fails is refused with offline_file_rejected and nothing is written.
  • Writes the file to the path atomically and creates parent folders. If the write fails, you get file_write_failed; calling again returns the same file for free.
  • Leaves the client in an offline session, exactly as after login_from_file: variables are available, no grace-period timer, no check-ins.
  • Never calls your failure callback and never exits the process. A failed activation leaves any existing session alone. You decide what to show.
login_from_file still reports a missing or rejected file through your failure callback as offline_login_failed in some SDKs. If you set a callback, let it return normally for that reason, or the app could exit before it reaches the key prompt.

4. Show clear errors

Every refusal is free: no credits are charged, no seat is bound and no machine is counted. The full list is in the Error codes reference.

5. Support buyers who change machines

The license can activate at most 5 distinct machines, ever (its HWID slots). The rules:
  • Same machine again (reinstall, deleted file, lost response): free, and the identical file comes back. It never counts twice.
  • New or changed hardware: counts as a new machine and costs 2 credits.
  • HWID resets do not help. A reset in the portal or dashboard frees seats, but never machine slots, because the old machine’s file keeps working.
  • Cap reached: the app gets offline_activation_limit_reached and tells the buyer to contact you.
When a buyer writes in, you can:
  • Release an offline machine on the license page in the dashboard, or with DELETE /v1/licenses/{licenseKey}/offline-machines/{hwidHash}. This frees one machine slot and that machine’s seat. The released machine’s file keeps working, so release only when you trust the request (for example “my old PC died”). GET /v1/licenses/{licenseKey}/offline-machines lists the machines.
  • Raise the license’s HWID slots in the dashboard (up to 16).
  • Mint a file by hand for a specific HWID (Offline license files). Hand-minted files do not count against the machine cap.

What this does not do

  • No time-limited licenses. Subscriptions and fixed-term licenses cannot self-activate. Use online login() and the grace period for those.
  • No unbound files at checkout. Every self-serve file is bound to one machine’s HWID.
  • No remote kill. Refunds, chargebacks and revocation stop new activations only. Issued files keep working.

Checklist

  • Self-serve offline activation turned on for the app
  • Stripe price mapped as a one-time purchase with blank access length and several HWID slots
  • Refund and dispute events subscribed, accepting that they cannot reach issued files
  • Buyer email on, with your support contact in the portal policy
  • App build has the App ID, App Secret and public key
  • Launch order: file, then trial, then key prompt + activate_offline with a path
  • Clear message for offline_activation_limit_reached that points to support
  • Credit balance or auto-refill for activations that happen long after the sale

Next steps