If your Chrome extension checks Polar license keys and every real key fails, look at the response of the activate call. When the license key benefit in your Polar dashboard has no activation limit, Polar answers with:
403 {"detail": "This license key does not support activations. Use the /validate endpoint instead to check license validity."}
Nothing is wrong with the key. The benefit simply has nothing to count, so Polar refuses to create an activation. Most examples call activate first and treat any non-200 as "invalid key", so the buyer sees an error with a key they just paid for.
There are two ways out.
1. Turn activations on. In Polar, open the product, edit the License Keys benefit and set an activation limit (for example 3 browsers). Now activate returns an activation id, and you validate later with that id. This is the only mode where Polar enforces a browser limit.
2. Fall back to validate. If you want unlimited use, keep the benefit as it is and handle exactly that 403:
const API = "https://api.polar.sh/v1/customer-portal/license-keys";
async function post(path, body) {
const res = await fetch(`${API}/${path}`, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({ organization_id: ORG_ID, ...body }),
});
return { status: res.status, json: res.status === 204 ? null : await res.json() };
}
export async function activateKey(key, label) {
const r = await post("activate", { key, label });
const noActivations =
r.status === 403 &&
typeof r.json?.detail === "string" &&
/does not support activations/i.test(r.json.detail);
if (noActivations) {
const v = await post("validate", { key });
if (v.status !== 200) return { ok: false };
return { ok: v.json.status === "granted", activationId: "" };
}
if (r.status !== 200) return { ok: false };
return { ok: r.json.license_key.status === "granted", activationId: r.json.id };
}
Three details that matter:
- Match that one 403 only. Any other 403 means something else and should still fail.
- Store an empty activation id, and on later checks call
validatewith noactivation_idfield at all. - Skip
deactivatewhen there is no activation id, since there is nothing to release.
These endpoints are the customer-portal ones, so they need only your organization id, no access token. Nothing secret ships in the extension.
We hit this while building Keyturn Kit, a WXT + React starter for paid Chrome extensions. Version 1.2.3 of its Polar provider (in Pro) handles both modes, with tests for the fallback. The free MIT edition on GitHub uses Lemon Squeezy.
Top comments (0)