Migrating from Keygen
Keygen is a mature, licensing-only API with a JSON:API surface, rich policies (including floating seats and MAINTAIN_ACCESS), and cryptographically signed license files. Licensr is a simpler REST API that bundles payments, a branded buy page, and a customer portal on top of seat/domain activation.
Use this guide if you already integrate Keygen and want to move validation (and optionally checkout) to Licensr.
Concept map
| Keygen | Licensr | Notes |
|---|---|---|
| Product | Plugin (plugin_slug) | Activation mode (seat / domain) is fixed at creation. |
| Policy | Plan | Pricing + seat cap + optional feature_flags + perpetual-fallback toggle. |
| License | License (lic_...) | Same idea; status is active / inactive / expired. |
| Machine | Activation (seat) | Domain-mode activations cover WordPress/web hosts Keygen doesn't model natively. |
| Entitlement resources | Plan feature_flags JSON | Returned on every validate/activate/token — no separate entitlement list endpoint. |
| Signed / encrypted license file | EdDSA JWT from POST /v1/license/token | Verify against the per-plugin JWKS. Shorter TTL, simpler verify path. |
MAINTAIN_ACCESS / expiration strategy | Perpetual fallback on the plan | Expired → valid:false, entitlement:"limited", fallback:true. |
| Floating / concurrent licenses | Not supported | Licensr is node-locked. |
| Your own Stripe / billing | Built-in checkout on your Stripe / Mercado Pago / PayPal | /buy/your-slug + subscription lifecycle included. |
See the full side-by-side at licensr.app/vs/keygen.
Replace JSON:API validate with one REST call
Keygen's validate-key / license-actions flow becomes:
import {LicensrClient} from '@licensr/sdk';
const client = new LicensrClient({
apiKey: 'pk_live_...',
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
}
Raw equivalent:
POST /v1/license/validate
Authorization: Bearer pk_live_...
Content-Type: application/json
{"license_key":"lic_...","plugin_slug":"my-plugin"}
There is no JSON:API compound document, no included array, and no separate "license action" resource. Always branch on valid / entitlement — an unknown key returns 200 {valid: false}, not 404.
Machines → seat activations
await client.activate({
licenseKey: userEnteredKey,
activationType: 'seat',
identifier: stableHwid, // reuse as deviceId for rate-limit bucketing
label: 'Build machine',
});
Deactivate via client.deactivate({licenseKey, activationId}) or let the customer do it in the branded portal. Fingerprint / component hashes from Keygen don't transfer — pick a stable host identifier (see §7 Identifier hygiene).
Entitlements → feature flags
Keygen entitlement resources become a single JSON object on the plan:
{
"pro_export": true,
"seat_limit_ui": 3
}
Returned as feature_flags on validate/activate/token. Prefer checking flags in-process over round-tripping a separate entitlement API.
Offline: license files → JWTs
Keygen's signed/encrypted license files are replaced by short-lived EdDSA JWTs:
client.token({licenseKey})while online.- Persist the token (JS
OfflineTokenStore, C++FileTokenStore, C#IOfflineTokenStore). - Verify locally against the JWKS URL on next launch.
import {verifyOfflineToken} from '@licensr/sdk';
const {token, jwksUrl} = await client.token({licenseKey});
const claims = await verifyOfflineToken(token, jwksUrl);
No RSA path and no encrypted-file scheme — Ed25519 verify only. Re-validate online before exp; offline tokens carry no live revocation signal.
Expiration strategies → perpetual fallback
If you relied on Keygen's MAINTAIN_ACCESS (or similar) so expired licenses keep a degraded tier:
- Enable perpetual fallback on the Licensr plan.
- On validate, read
entitlement === 'limited'+fallback === true(status stays"expired",validstaysfalse). - Older clients that only check
validcontinue to fail closed.
Deliberate cutoffs (refund, admin deactivate) always return entitlement: "none".
What you gain that Keygen doesn't ship
- Hosted buy page and automatic key email on your own payment account.
- Subscription lifecycle wired to license state — no custom Stripe webhook worker.
- Branded customer portal for seats and billing.
- Lower entry price for the same validate/activate core (see pricing).
Migration checklist
- Create the plugin + plans; encode Keygen entitlements as
feature_flagsand map expiration strategy → perpetual fallback. - Issue a client-scoped
pk_live_...and embed it via an official SDK (or raw REST). - Replace JSON:API validate/machine calls with
/validate+/activate. - Swap offline license-file verify for JWT + JWKS.
- Optionally move new checkout to
/buy/your-slug. - Re-issue licenses for the existing cohort — Keygen keys are not portable.
- Run both systems in parallel until activations tip over.