Migration guide

Move from Keygen to Paperkey in 10 minutes.

You export your existing licenses from Keygen, transform the JSON to the Paperkey shape, POST them in. The SDK call site in your shipped app stays the same. The dashboard, the webhooks, and the audit log come up populated on the first reload.

~10 min for under 500 licenses · ~1 h for under 10k · scripted for 10k+

Concept mapping

Most concepts map one-to-one. The names differ in three places (Account, Policy, Token).

  • Keygen
    Account
    Paperkey
    Workspace (account)
    Note
    Top-level isolation. One per company.
  • Keygen
    Product
    Paperkey
    Product
    Note
    Same idea. Carries config (cap, fingerprint policy).
  • Keygen
    Policy
    Paperkey
    Product config
    Note
    Inlined into the product. Per-license overrides on the License.
  • Keygen
    License
    Paperkey
    License
    Note
    Same. Arbitrary key formats accepted on import.
  • Keygen
    Machine
    Paperkey
    Activation
    Note
    Same. Hashed fingerprint, slot of cap.
  • Keygen
    Token (license token)
    Paperkey
    Validation token
    Note
    JWT issued by validate. Compatible drop-in shape.
  • Keygen
    Webhook
    Paperkey
    Webhook event
    Note
    HMAC-SHA-256, hex signature, 5s timeout.
  • Keygen
    Entitlement
    Paperkey
    License metadata
    Note
    Stored as JSON on the license. Read by the SDK.

Four steps

1. Export from Keygen

Use the Keygen API to dump every license into a single JSON file. Replace <account-id> and <keygen-api-token> with your values.

curl -X GET 'https://api.keygen.sh/v1/accounts/<account-id>/licenses' \
  -H 'Authorization: Bearer <keygen-api-token>' \
  -H 'Accept: application/vnd.api+json' \
  > keygen-licenses.json

2. Transform to the Paperkey shape

A small Node one-liner. Reads keygen-licenses.json, projects every license into the Paperkey input format, writes paperkey-licenses.json. No external dependencies.

node -e '
  const data = JSON.parse(require("fs").readFileSync("keygen-licenses.json", "utf8"));
  const out = data.data.map(l => ({
    key: l.attributes.key,
    customerEmail: l.relationships?.user?.data?.id ?? null,
    expiresAt: l.attributes.expiry,
    activationCap: l.attributes.maxMachines ?? 1,
    metadata: l.attributes.metadata ?? {},
  }));
  require("fs").writeFileSync(
    "paperkey-licenses.json",
    JSON.stringify(out, null, 2)
  );
'

3. Import into Paperkey

POST each license to /v1/licenses. Plain bash + jq. The endpoint is idempotent on the key, so you can re-run safely if a request times out.

jq -c '.[]' paperkey-licenses.json | while read -r row; do
  curl -X POST https://api.paperkey.dev/v1/licenses \
    -H "Authorization: Bearer pk_<your-api-key>" \
    -H 'Content-Type: application/json' \
    -d "$row"
done

4. Update the SDK config

In your shipped app, point the SDK at api.paperkey.dev with the new public verification key. The SDK call site stays the same. Customers re-activate seamlessly the next time their app phones home.

import { Paperkey } from '@paperkeyhq/sdk';

const pk = new Paperkey({
  apiUrl: 'https://api.paperkey.dev',
  productId: 'prod_<your-product-id>',
  publicKey: '<your-paperkey-public-verify-key>',
});

// Same call site as before. The SDK handles cache + offline grace.
const verdict = await pk.validate(licenseKey);

Known limitations

Honest list. We tell you upfront so you do not discover them in production.

  • Machine fingerprints are regenerated on first activation against Paperkey. Existing Keygen machine slots are translated to "available capacity" on the license, not pre-bound to specific machines.
  • Custom Keygen webhooks are not auto-replayed. Re-subscribe in the Paperkey dashboard with the same target URLs; we will resend on every state change going forward.
  • Complex entitlements (per-feature flags) come over as JSON metadata on the license. The SDK exposes them with `license.metadata.<key>`. Refactor your feature gates accordingly.
  • License analytics history (charts, time series) does not migrate. The Paperkey audit log starts on the day of import.

Need a hand?

If you have more than 10k licenses, complex entitlements, or just want eyes on the migration, write to us with your Keygen account size and a contact time. We do walkthroughs in 30 minutes against your real data, free.

Email hello@paperkey.dev

Still evaluating?

Read the 5-minute quickstart. Or skip it: the SDK has decent defaults and the dashboard explains itself.