Skip to content

Guides

Recipe: License keys

Sell a desktop app, CLI, plugin or template with license keys. The shipped app checks its key with no secret inside.

View as Markdown

Perly Pay issues a key when the payment is fulfilled, counts activations per device, and answers "is this key good for this product here?" through public endpoints. The software you ship uses LicenseClient, which needs no secret key.

With an assistant: the sell-license-keys skill of the Perly Pay plugin for Claude Code does this recipe in your project — the plugin and its example, examples/license-keys (MIT), are on their way. Until then, any assistant can read the same skill from /.well-known/skills/perly-pay-sell-license-keys/SKILL.md.

How it works

  1. 1

    A product that issues keys

    issues_license: true and license_activation_limit (devices per key), with a one-time, lifetime, fixed-term or subscription price.

  2. 2

    Sell it

    A payment link on your site, or an invoice per buyer.

  3. 3

    Send the key

    On the license.created webhook (its object is the key), or licenseKeys.list({ customer }) after invoice.fulfilled. The receipt does not carry it.

  4. 4

    Activate in the app

    activate({ key, product, instance_name }) with a name that stays the same on this device; keep the activation id.

  5. 5

    Check on start

    validate({ key, product, instance }). A definite valid: false locks; no network keeps the last good answer for a few days.

import { LicenseClient } from "perly-pay";

const licenses = new LicenseClient(); // no secret key, ever
const result = await licenses.activate({ key, product: "prod_…", instance_name: deviceName });
if (!result.valid) showWhy(result.reason); // limit_reached, revoked, expired, …

Run the example

  1. 1

    Install

    npm install in examples/license-keys (Node 20.12+).

  2. 2

    Key

    Copy .env.example to .env and put a test key (sk_test_…) in PERLY_PAY_SECRET_KEY.

  3. 3

    Set up

    npm run setup makes the product and price with your key and prints their ids for .env.

  4. 4

    Run

    npm start (or npm run dev), as its README says.

  5. 5

    Pay without a wallet

    npm run buy-test -- you@example.com buys a copy in test mode and prints the key; then npm run app -- activate <key> and npm run app -- pro hello.

Pitfalls

  • A sk_… key inside the app. Anyone can pull it out; shipped code uses LicenseClient only.
  • Leaving out product: then a key for anything you sell unlocks this.
  • An instance_name that changes every run: each start eats an activation.