Migrating to Keygate
Move your customers from another licensing system to Keygate: export, import with a script, switch your app and move subscriptions.
Moving from Keygen, Cryptlex, LicenseSpring or a system you built yourself follows the same steps: recreate your plans, import your customers, and ship an app version that talks to Keygate. Most of the work is planning how your customers move over, not the import itself.
Before you start
Keygate issues its own license keys, so every customer gets a new key. Keys from your old system cannot be imported. That decides the rest of the plan:
- Create your products and plans in Keygate first. See Licensing models to map your old plans onto Keygate's license types.
- Keep the old system running until most active customers have moved, so nobody is locked out by the switch.
- Edit the license email under Email Templates before you import. Every imported license sends the customer their new key, and that email is the best place to explain why they got one.
Export your customers
Export one row per license from your old system: the email, which plan it is on, when it ends, and its ID in the old system. Leave the end date empty for licenses that never end.
email,plan,expires,customer_id
[email protected],pro,2027-03-01,cus_1042
[email protected],lifetime,,cus_1043Import them into Keygate
Create an API key with the licenses:write scope, limited to the product you are importing into. Then run this script with Node.js 18 or later, or with Bun. It creates one license per row and keeps the old ID in external_customer_id, so running it again skips anyone it already imported.
import { readFileSync } from "node:fs";
const KEYGATE = process.env.KEYGATE_URL; // https://licenses.example.com
const API_KEY = process.env.KEYGATE_API_KEY; // kg_live_... with licenses:write
const PRODUCT_ID = process.env.PRODUCT_ID;
// Old plan name on the left, Keygate plan ID on the right.
const PLANS = {
pro: "8c1e2b9a-...",
lifetime: "41f07d3c-...",
};
const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };
const [, ...lines] = readFileSync("customers.csv", "utf8").trim().split("\n");
for (const line of lines) {
const [email, plan, expires, customerId] = line.split(",");
// Skip customers imported by an earlier run, so the script can be run again.
const existing = await fetch(
`${KEYGATE}/api/v1/admin/licenses?external_customer_id=${encodeURIComponent(customerId)}`,
{ headers },
).then((r) => r.json());
if (existing.data.total > 0) {
console.log(email, "already imported");
continue;
}
const res = await fetch(`${KEYGATE}/api/v1/admin/licenses`, {
method: "POST",
headers,
body: JSON.stringify({
product_id: PRODUCT_ID,
plan_id: PLANS[plan],
email,
external_customer_id: customerId,
// Keep the end date from the old system. Empty means it never ends.
valid_until: expires ? `${expires}T23:59:59Z` : "",
}),
});
const body = await res.json();
console.log(email, res.ok ? body.data.license_key : `failed: ${body.error?.code}`);
}Switch your app
Release a version of your app that activates against Keygate, following Activating licenses in your app. On first start it finds the key saved by the old version. Keygate keys start with KG-, so anything else is an old key: show a short message asking for the new key from the email, with a link to the customer portal for anyone who cannot find it.
Customers who never update keep using the old system for as long as you run it. Shut it down once activity there has dropped to a level you are comfortable with.
Subscriptions
Imported licenses are not connected to the subscriptions in your old billing setup. The end date you imported keeps each customer licensed for the time they already paid for. Before it runs out, send them a Keygate checkout link to subscribe again; from then on Keygate renews their license automatically. Cancel each old subscription as its customer moves, so nobody is charged twice. See Selling with Stripe.
Last updated October 4, 2026