Skip to main content

Migrating from LicenseSeat

LicenseSeat is a license server with native SDKs (Swift, Rust, C#, C++, JS) and a customer portal. It issues keys from a checkout you already run. Licensr covers the same validation and seat-activation surface, then hosts the buy page and the payment account LicenseSeat leaves to Gumroad, Paddle, Stripe, or another processor.

Use this if you already ship against LicenseSeat and want validation (and optionally checkout) on Licensr. Create the product and Client API key in the admin panel first; the snippets below replace LicenseSeat SDK calls.

Concept map​

LicenseSeatLicensrNotes
Product / appPlugin (plugin_slug)Fixed at creation; carries activation mode (seat or domain).
License keyLicense key (lic_...)Same idea — a secret string the end user enters.
Activation / nodeActivation (seat or domain)Licensr also supports domain-mode for WordPress/web plugins.
CheckEntitlement("name")Plan feature_flags JSONReturned on /validate and /activate, and inside the token's claims. Gate in-process; no extra round-trip.
Signed "machine file" (offline)EdDSA JWT via POST /v1/license/tokenVerify against the per-plugin JWKS. See offline signed tokens.
Floating / concurrent seatsNot supportedLicensr uses node-locked seats. Plan seat caps, don't heartbeats.
Your Gumroad / Paddle / Stripe / Lemon Squeezy / Shopify / PolarBuilt-in checkout on your Stripe / Mercado Pago / PayPal / RazorpayLicenseSeat issues keys from those stores. Licensr hosts /buy/your-slug. 0% revenue share.

See the full side-by-side at licensr.app/vs/licenseseat.

Replace the validate call​

LicenseSeat SDKs typically expose something like client.validate(key) / CheckLicense. Against Licensr the same check is one REST call (or one SDK method):

import {LicensrClient} from '@licensr/sdk';

const client = new LicensrClient({
apiKey: 'pk_live_...', // create under Admin → Plugin → API keys
pluginSlug: 'my-plugin',
});

const result = await client.validate({licenseKey: userEnteredKey});
if (result.valid) {
unlockFullFeatures(result.featureFlags);
} else if (result.entitlement === 'limited') {
unlockDegradedMode(); // perpetual-fallback plan
}

Equivalent raw request:

POST /v1/license/validate
Authorization: Bearer pk_live_...
Content-Type: application/json

{"license_key":"lic_...","plugin_slug":"my-plugin"}

Always branch on valid / entitlement, never on HTTP status — an unknown key returns 200 {valid: false}, not 404.

Seats and activations​

  1. On first successful validate (or on an explicit "Activate" UI), call POST /v1/license/activate with activation_type: "seat" and a stable machine identifier.
  2. Pass the same identifier as the SDK deviceId / X-Device-Id so rate limits bucket per installation.
  3. Let customers free a seat themselves via the branded portal — you rarely need deactivate in-product.
await client.activate({
licenseKey: userEnteredKey,
activationType: 'seat',
identifier: stableHwid,
label: 'Studio iMac',
});

C++, C# and Go hosts: see the C++ SDK (licensr::compute_hardware_id), C# SDK (Unity SystemInfo.deviceUniqueIdentifier, or a persisted GUID) and Go SDK (licensr.ComputeHardwareID, same value as C++).

Entitlements → feature flags​

LicenseSeat's named entitlement API becomes a JSON object on the plan:

{
"auto_backup": true,
"export_limit": 100
}

Set it under Admin → Plan → Advanced options. Validate and activate return it as feature_flags, and the token carries it inside its claims. Default gracefully when a flag is missing so older plans keep working.

Offline​

  1. While online: client.token({licenseKey}) (or POST /v1/license/token).
  2. Persist the JWT (JS: OfflineTokenStore; C++: FileTokenStore; C#: IOfflineTokenStore; Go: TokenStore).
  3. On next launch, verify locally against the JWKS URL before any network call.

Offline tokens prove issuance + expiry, not live revocation — re-validate online before exp.

What you gain that LicenseSeat doesn't ship​

  • Hosted buy page at /buy/your-slug on your own payment account. LicenseSeat leaves checkout on your existing store.
  • Subscription lifecycle (renewals, grace, cancel → license state). LicenseSeat extends expiry on renewal and can revoke on refund; a cancellation or failed payment does not revoke.
  • Billing inside the portal (Stripe Customer Portal and plan changes) on top of seat self-serve. LicenseSeat already has a license portal for key recovery, device transfer, and renewals.
  • Domain-mode activation + Origin guard for web/WordPress plugins.

Migration checklist​

  1. Create the plugin + plan(s) in Licensr; copy feature_flags from your LicenseSeat entitlement names.
  2. Issue a pk_live_... (client-safe scopes) and embed it via the SDK of your host language.
  3. Swap validate/activate calls; keep your existing key-entry UI.
  4. Optionally cut over checkout to /buy/your-slug and retire Gumroad/Paddle for new sales.
  5. Re-issue licenses for existing customers (export → bulk import, or a one-time script) — keys are not portable across vendors.
  6. Keep LicenseSeat running in parallel until the cohort has activated on Licensr at least once.

Further reading​