Verification at the edge
A license check is a signature check plus a cache lookup. Both happen in the Worker that is already serving the request — usually before the first byte leaves the region.
Keyline is a small open-source library that issues, verifies and meters licenses inside the Cloudflare Worker you already run. No database to babysit, no dashboard to log into, no invoice at the end of the month.
npm i @keyline/edge
Licensing is a solved problem with an unsolved bill. Keyline keeps the logic — signing, entitlements, seats, metering — and hands the running of it to the edge you already pay for.
A license check is a signature check plus a cache lookup. Both happen in the Worker that is already serving the request — usually before the first byte leaves the region.
The same license card, the same component, a second theme. Ship a dark-mode account screen without maintaining two design systems.
Meter what you sell — per seat, per call, per build. Counters live next to the license, not in a spreadsheet.
Signed, retried, idempotent. Refunds, cancellations and chargebacks arrive as ordinary events you can replay.
Licenses are self-contained and signed. If the network disappears, your customer keeps working and you keep the audit trail.
No SDK to configure, no middleware to mount. Import, verify, continue — and let the request fall through exactly as before.
import { Keyline } from "@keyline/edge";
export default {
async fetch(request, env, ctx) {
const key = request.headers.get("x-license-key");
const license = await Keyline.verify(key, env.KEYLINE_SECRET);
// expired, revoked, or out of seats
if (!license.valid) {
return new Response("Payment required", { status: 402 });
}
ctx.waitUntil(Keyline.report(license, env.KEYLINE_SECRET));
return env.ASSETS.fetch(request);
},
};
Issue once at checkout, verify on every request, meter in the background. That is the entire lifecycle.
When a payment succeeds, sign a license payload with your secret and hand it to the customer. No rows to insert, no state to migrate.
Keyline.issue({ seats: 50 })Every request carries a key. Verify the signature at the edge, check entitlements, and reject with a plain 402 when it no longer holds.
Keyline.verify(key, secret)Usage and seat counts flow back asynchronously. You get billing-grade numbers without adding latency to the customer's request.
Keyline.report(license, secret)Everything here exists because a founder hit it in production and opened an issue. It ships in about 40 kB.
Licenses carry their own proof. Nothing breaks when your API or ours is unreachable.
Invite, deactivate and reassign seats without inventing your own invite flow.
Refund, chargeback or abuse — revoke a key and the edge cache follows within one TTL.
Expire softly. Customers keep their data and their dignity while you keep the renewal.
First-party webhook handlers. Point the endpoint at your Worker and you are done.
Every issue, verify and revoke is an append-only event stream in your own storage.
One schema generates the runtime validators, the SDK types and the dashboard forms.
Fork it, vendor it, self-host it. No open-core asterisk, no seat-based licence for the licence server.
Most of Keyline's traffic is invisible — a signature check on the way to a static asset. Here is what that looks like at scale.
Indie developers, small studios and a few companies you would recognise.
Across 4,100 Workers in 91 countries — none of them hitting a central database.
p95 stays under 70 ms globally, including the cold path on a fresh region.
No hosted tier required to be useful. Self-host in an afternoon if you prefer.
No. A license is a signed payload, so verification only needs your public key. If you want seat counters and revocation, point Keyline at any KV or Durable Object you already have — it never asks for its own server.
Ed25519, detached, over a canonical JSON encoding. The private key stays in a Worker secret; the public key is pinned in your client. Keys look like KLY-7F2A-9C41-D8E0 and are only a lookup handle — the signature is what grants access.
The license still verifies offline. Revocation is the only thing that needs the network, so it is cached with a short TTL and a configurable grace window. Worst case, a revoked key keeps working until the window closes — which is the same trade-off every licensing system makes.
Yes. The verifier is a library, the issuer is a library, and the dashboard is a static site. There is no hidden control plane phoning home; the only network calls are the ones you configure.
The core is runtime-agnostic — Node, Deno, Bun, Vercel and Netlify all pass the same test suite. The Cloudflare adapter is simply the one with a KV cache and a Durable Object for seat counters.
Nothing. It is MIT licensed and there is no hosted tier to upsell you into. If it saves you a weekend, the maintainers would rather have a bug report than a subscription.