Changelog
0.25.1
Section titled “0.25.1”2026-09-07
SDK releases only — nothing changed on the server. What the Swift SDK gained over the last week now works the same way in Rust, JavaScript, C# and C++.
- Every SDK can verify that your settings really came from your dashboard. The trial length and free-tier flag have been signed since 0.25.0; now each SDK checks that signature when you turn on
requireSignedConfig(require_signed_configin Rust,RequireSignedConfigin C#). It is off by default, because Keylight signs an app’s settings only once that app has a Trial length set in the dashboard — set one, then switch it on. See Verify the signature. - Your customers see their upgrade within seconds, in every SDK. Payment webhooks can lag the checkout redirect.
refreshAfterUpgrade(and its language spellings) re-checks briefly when the customer returns and unlocks the new tier as soon as it lands, instead of waiting for the next scheduled check. - The trial length in force is readable from any SDK.
effectiveTrialDurationDays,effectiveFreeTierEnabledandfetchConfigexist on the type your app holds in all five.
Swift SDK 0.12.1
Section titled “Swift SDK 0.12.1”requireSignedConfigis a parameter ofKeylight.manager(...). 0.12.0 added the switch but only on the long-form configuration, so the factory almost every app calls could not turn it on. Upgrade if you are on 0.12.0.
JavaScript SDK 0.4.2
Section titled “JavaScript SDK 0.4.2”refreshAfterUpgradestops when you tell it to. Passing anAbortSignalnow interrupts a wait in progress, the poll never runs past its timeout, and a pending poll no longer keeps a Node process alive.requireSignedConfigis documented, including thattrustedKeysmust be pinned at build time when it is on.
C# SDK 0.4.1
Section titled “C# SDK 0.4.1”RefreshAfterUpgradeAsyncandEffectiveFreeTierEnabled, so a .NET or Unity app unlocks a purchase within seconds and can read the free-tier flag in force — the same calls the other SDKs have.RequireSignedConfigis safe under every culture. The signed payload was formatted with the current culture; it is now invariant, so verification cannot differ by the user’s locale.
Rust SDK 0.6.1
Section titled “Rust SDK 0.6.1”- Tauri commands no longer block the window.
refresh_after_upgradeand every other command that touches the network run off the main thread; on 0.6.0 a post-purchase poll could freeze the UI for up to 30 seconds. Upgrade if you ship a Tauri app. tauri-plugin-keylight-apion npm is back in step with the crate.
C++ SDK 0.2.2
Section titled “C++ SDK 0.2.2”- Clean under sanitizers. Two undefined-behaviour sites reachable from a server response are fixed, so an app built with
-fsanitize=undefined,addressno longer aborts on the first license check. JUCE and Unreal integrators who build with sanitizers should upgrade. - Settings learned on the beacon apply immediately, instead of on the next scheduled check.
- With
requireSignedConfigoff, a signature that happens to be present is ignored, as in the other four SDKs. Nothing changes with it on.
0.25.0
Section titled “0.25.0”2026-09-06
- Your dashboard now decides the trial length. Until now the number compiled into your app was the only one that ever applied — you could set a trial length in Keylight and nothing happened. Trial length and Free tier on an app’s Access card are now the values that apply, delivered on calls the SDKs already make, so changing one reaches installed copies on their next check with no release. The value you build with becomes the seed a brand-new install uses before it first reaches us. See Trial length and free tier.
0means trials are off, and sticks. It is a real setting rather than a blank, and stays distinct from never having configured one — which leaves your compiled-in length in charge. Turning trials off no longer looks like never having set them.- Deleting the app’s data no longer earns a second trial. Keylight now remembers that a device started one, so the oldest trick for farming trials stops working on an unmodified app. It is built to fail open: a device mid-trial, an unknown device, one that evaluated over 18 months ago, and — deliberately — every device behind a shared machine id all still get their trial. Imaged laptop fleets and cloned VMs are far more often a company than a cheat, so that case alerts rather than locking anyone out. See Server-side trial memory.
- The settings are signed.
GET /configreturns an Ed25519 signature over the trial length and free-tier flag, bound to your tenant, your product and a 24-hour window, so something sitting on the network path cannot answer with an unlimited trial. If you write your own client, verify it. At this release the SDKs did not enforce it; they do since 0.25.1, behind a switch. Keep paid functionality behind an activated licence either way — its lease is verified on every read. GET /configis documented, and now actually routes. The endpoint that serves these settings was never reachable byGET; it is, and it has a reference page with the signature format and the checks to run.validateand the keyless beacon document what they were already returning. Both have carried the settings since the SDKs started reading them; their reference pages now list the fields, plustrial_started_atandtrial_already_usedon keyless. Absent is not zero and absent is not false — the pages say which is which.- Trials are explicitly not part of API integration. They are what an unlicensed install gets, and an API integration validates licences from your own server, where that policy is yours to run. The dashboard control is switched off for API apps instead of sitting there doing nothing, and the quickstart explains the split.
Swift SDK 0.11.1
Section titled “Swift SDK 0.11.1”effectiveTrialDurationDays(),effectiveFreeTierEnabled()andfetchConfig()onLicenseManager. Read the length actually in force — server value, then your compiled-in value, then0. No new network calls at launch; the settings ridevalidateand the keyless beacon. See Trial length and free tier.- Upgrade if you are on 0.11.0. That release announced the settings but never forwarded the three methods to
LicenseManager, somanager.fetchConfig()did not compile and the values were unreachable from the type most apps hold. - A server value of
0survives as0, rather than being read as “unset, use the compiled-in seed”.
0.24.0
Section titled “0.24.0”2026-08-30
- Seats now work on every provider, not just Stripe. A buyer choosing five copies at checkout gets one five-times key whether they paid through Stripe, Paddle, Lemon Squeezy, Polar or Gumroad. Each provider reports the count on the payment and Keylight multiplies the device limit by it, so a studio buying in bulk is one key to issue and one key to support.
- Buying more seats later works on the merchant-of-record providers too. The customer portal’s Add seats button now reaches Paddle, Lemon Squeezy and Polar alongside Stripe. Gumroad is the exception — it has no way to build a checkout with a count attached, so its buyers choose their number on the first purchase. Where adding seats isn’t possible the button doesn’t appear, rather than appearing and failing.
- A tier billed as a subscription can no longer be bought as a one-time upgrade. That combination took the payment without ever creating a subscription, so the tier lapsed at what should have been its first renewal. It is now refused with a clear message while subscription upgrades are designed properly.
- Creem and Shopify are no longer supported. Neither had ever issued a license through Keylight. Both are removed rather than left in the list as options that look available; the five remaining providers are Stripe, Paddle, Lemon Squeezy, Polar and Gumroad. If you were planning on either, get in touch.
0.23.1
Section titled “0.23.1”2026-08-29
- Buyers choose how many seats on your buy URL. A key type that sells in multiples now shows a seat chooser on its Keylight buy URL — until now it always sold exactly one, whatever the buyer wanted. Link straight to
?qty=5to skip the chooser if you already have your own picker, or passquantitytoPOST /api/{tenant}/checkout. The count is chosen on Keylight’s side rather than in Stripe Checkout, so the platform fee is always computed on the real line total. - A live price list for your own pricing page. A new public endpoint returns every purchasable key type for an app — price, currency, term, devices per seat, maximum seats and buy URL — so a pricing page you build yourself can’t end up quoting a number your checkout no longer charges. No token needed, so it’s safe to call from the browser. See Pricing.
- Apps can be deleted. A Danger zone on the app page removes an app and cleans up after it: Stripe products and prices archived, price mappings purged from every provider and environment, free-tier device rows dropped. Offered only while an app has never issued a license — once there’s history, deleting the app would leave licenses that a later app of the same name could adopt. Financial and audit history is never deleted.
- Price edits now reach checkout, and reach the right account. Three ways an edited price could quietly fail to apply are closed: a Payment Link pasted once kept selling at its original price no matter what you changed, an app moved to its own connected account had new prices minted on the shared one, and a subscription key type could be given a one-time price that checkout then refused. If you edited a price and the old one kept being charged, that was this.
- Your checkout page now says what the buyer is getting. Each key type backs its own Stripe product carrying a plain description —
Up to 5 devices · Monthly,Up to 2 devices per seat · 1 year— so the two facts a license buyer needs are on the page. Before, checkout could show only the app name and a price. Existing key types migrate on their next catalog sync. - Connected Stripe accounts are named, and say who they belong to. Settings → Integrations lists one row per connected account with its name in Stripe, and the Manage Stripe Connect modal now states whether an account is dedicated to this app or inherited from your shared account. Per-app sandbox accounts are no longer invisible.
0.23.0
Section titled “0.23.0”2026-08-28
- Sell your app in multiples. A new Sell in multiples switch on a key type turns its device limit into a per-unit figure, so a studio can buy five copies of your 3-device app in a single checkout and receive one 15-device key — no hand-authored “Team 15” tier, and one key to support instead of five. Stripe only for now: a merchant-of-record checkout can’t carry a quantity. See Sell in multiples.
- Customers can buy more seats without changing tier. A license sold in multiples gets an Add seats button in the customer portal — more of what they already own, applied to the same key. The key string never changes, so there’s nothing for them to re-enter in your app.
- Subscription seat counts stay in step. Add seats to a subscription and the higher limit is live immediately. Reduce them and the change waits until the end of the period already paid for — with an email and a portal banner naming how many devices to remove and by when, and a chooser so the customer decides which ones. If the deadline passes with no choice made, the least recently used devices come off automatically, so a license is never left over its limit.
- Tier changes keep the seats. A customer who bought three seats and then moves tier lands on three seats of the new tier, rather than dropping to that tier’s base device limit.
0.22.1
Section titled “0.22.1”2026-08-14
- The Keylight CLI. Your whole account from a terminal instead of the dashboard — apps, key types, licenses, customers, payment integrations and usage. Create an app, mint a key, revoke a leaked one, or look up a customer’s license in the middle of a support ticket without opening a browser. Install it with Homebrew (
brew tap keylight-dev/tap,brew trust keylight-dev/tap, thenbrew install keylight) orcargo install keylight-cli, thenkeylight login. See Install and Commands. - Built to be run by something other than a person. No command opens a prompt, every input is a flag, and every command speaks
--json— so a release pipeline or a coding agent can drive the same surface you do. While the CLI is on0.x, flag names and human-readable output can still change; the--jsonoutput and the exit codes are the parts to script against. - Personal access tokens and the management API behind them. Scoped tokens with an optional expiry, issued from the dashboard, against the same
/v1API the CLI and the dashboard both use — so anything you automate shows up in the dashboard and anything you do in the dashboard is visible to your scripts. Dangerous operations ask for approval in the browser before they run. See Management API.
0.22.0
Section titled “0.22.0”2026-08-09
- License your app over plain HTTPS — no SDK required. Keylight now has a first-class API integration mode: a server-side app (a SaaS backend, a CLI, a daemon) can activate, validate and deactivate licenses with a
POSTand clean JSON back — no SDK to install, no lease to decode. See the API integration quickstart. - Choose SDK or API when you create an app. New apps now start with a clear choice — ship a Keylight SDK for desktop, mobile and native apps (verifies signed licenses on the device, works offline), or call the HTTP API directly for servers and web apps. The dashboard, the setup wizard and the AI-agent onboarding all follow your choice.
- A dedicated API app page. API-mode apps get their own setup panel — the runtime credential, a copy-ready cURL quick start, every endpoint, and one-click webhook setup — with a clear reminder that the token is server-side only.
0.21.2
Section titled “0.21.2”2026-08-05
- Usage figures had gone stale, and are correct again. The nightly rollup behind the Usage tab stopped writing on 2026-07-16, so for three weeks the page served a monthly-active figure that was quietly out of date. No counts were lost — the missed days were backfilled from data that had been held all along.
- “Devices seen” counted one app, not your account. If you ship more than one app, the device cards reported whichever app sorted first while activations and validations summed across all of them, so the two numbers described different things. Both now cover every app.
- Device numbers stay put. A device’s number was assigned by its position in the page you happened to be looking at, so the same Mac changed number as the recently-seen order shifted. Numbering is now fixed per device, in the order you first saw it.
- The free-tier device list goes past 50. The table stopped at 50 devices with no way to reach the rest. It now pages through all of them, and the header states the real total.
0.21.1
Section titled “0.21.1”2026-07-25
- Lemon Squeezy sandbox and live environments. Connecting a Lemon Squeezy environment now stores that environment’s signing secret and API key in its own slot, so you can run a sandbox rehearsal and a live integration side by side — the same way Paddle and Polar already work.
- Switching on a second environment no longer disturbs the first. Adding a sandbox environment to an existing integration keeps the live signing secret and price mappings exactly as they were.
0.21.0
Section titled “0.21.0”2026-07-24
- Test a real checkout on Lemon Squeezy and Polar too. The end-to-end sandbox purchase rehearsal now works for Lemon Squeezy and Polar, not just Stripe and Paddle — run a real test-mode checkout and watch each stage light up (checkout, webhook, license minted) before you go live. Providers that can’t run a real test checkout stay clearly marked and fall back to Simulate, so nothing is ever faked.
- Sandbox and live connections for Lemon Squeezy and Polar. Both providers now keep separate sandbox and live credentials side by side, so you can keep testing after going live without reconnecting — the same dual-environment setup Stripe and Paddle already have.
0.20.0
Section titled “0.20.0”2026-07-23
- Paddle payments. Paddle is now a production-ready payment provider alongside Stripe — connect Paddle as your merchant of record and sell licenses through Paddle checkout.
- Test a real checkout before you go live. The setup wizard now runs an end-to-end sandbox purchase: run a test-mode checkout (or simulate one) and watch each stage light up — checkout, webhook, license minted — so you can confirm the whole flow and mint your first license before switching to live.
- Stripe Connect: sandbox and live, on every plan. Your sandbox and live Stripe connections now coexist, so you can keep testing after going live without reconnecting — and full Stripe Connect is available on every plan, Free included. Choose exactly which apps sell through your connected account, each with its own hosted Buy URL.
- See where your activations come from. A new world-map country breakdown on the dashboard shows where your customers are, and the analytics tables now paginate.
- Straight in after verifying your email. Verify your email at signup and you’re signed in automatically — no second sign-in step.
- More reliable subscription renewals. Subscription renewal handling is now hardened against out-of-order and replayed billing events, so renewal expiry dates stay correct.
- Refreshed typography. The marketing site and dashboard now share the same type system for a more consistent look across Keylight.
0.19.0
Section titled “0.19.0”2026-07-07
- Rust SDK — show your app version in the dashboard. The Rust SDK attaches
sdk_versionandplatformautomatically, but the app version must be set explicitly with.app_version(env!("CARGO_PKG_VERSION"))on the config builder — otherwise it’s omitted from every request (including the free-tier/keyless beacon) and shows blank in your dashboard. This wasn’t spelled out before; the Rust SDK install guide and the crate README/docs now call it out, with the builder examples updated. Shipping inkeylight0.3.1. (Swift already sends the app version automatically.) - Platform & SDK version for free-tier devices. Anonymous keyless / free-tier beacons now record each device’s platform and SDK version — previously these were kept only for activating/validating licensed instances and dropped on the keyless path. A new Free-tier devices table on the Analytics page lists recent anonymous devices with their app version, SDK version, and platform, so you can see what your free users are running.
0.18.0
Section titled “0.18.0”2026-06-13
- Complete HTTP API reference. Every public endpoint is now documented — activate, validate, deactivate, the free-tier and keyless beacons, and the public keyset — each with curl and SDK examples, so you can integrate from any platform that speaks HTTP without an SDK. See API reference.
- Try activation live in the docs. A new interactive demo lets you activate a real license key in your browser and watch entitlements flip in real time, powered by the JavaScript SDK. Open the live demo.
0.17.0
Section titled “0.17.0”2026-06-12
- JavaScript/TypeScript SDK. A first-party JS/TS SDK for the browser, Node, Deno, Bun, Electron, and edge/Workers — one fully-typed package. Online activation plus the same offline Ed25519 lease verification as the Swift and Rust SDKs, so a license verifies identically everywhere. Install
@keylight-dev/js(npm).
0.16.0
Section titled “0.16.0”2026-06-11
- Rust SDK. A first-party Rust SDK for CLIs, daemons, and desktop apps — including a Tauri plugin. Synchronous and runtime-free (no
async/Tokio), with offline Ed25519 lease verification built in. Installkeylight(crates.io).
0.15.2
Section titled “0.15.2”2026-06-10
- Swift SDK 0.8.1 — version & platform insights. The SDK now reports anonymous app version, SDK version, and platform on activation and validation, so your Usage dashboard can break activity down by app version and platform. It’s automatic — update to 0.8.1 with no code changes. Binary via keylight-swift 0.8.1; see the Swift SDK install guide.
0.15.1
Section titled “0.15.1”2026-06-07
- Swift SDK 0.8.0 — choose your storage backend from the factory.
Keylight.manager(...)now takes an optionalstorage:parameter, so the 0.6.0 storage options are reachable in one line:storage: .encryptedFile(keychainMirror: true)to also keep a Keychain recovery copy, orstorage: .keychainfor the legacy Keychain-authoritative backend. The default is unchanged (.encryptedFile(), no Keychain popup) — leave it off and nothing changes. Binary via keylight-swift 0.8.0; see Storage.
0.15.0
Section titled “0.15.0”2026-06-06
- Free is the floor. Stop paying and your account drops to the active Free tier instead of being locked out — you keep using Keylight within the Free limits.
- Annual billing. Pay yearly and save about two months versus monthly.
- Clearer checkout errors. A misconfigured upgrade now shows a specific reason instead of a generic failure.
- Onboarding improvements. A round of UX polish across setup, plus a few broken links fixed.
0.14.2
Section titled “0.14.2”2026-06-05
- Swift SDK 0.7.0 — trial, free tier, and expired are now distinct states. On a free-tier product, a brand-new keyless install resolves to
.freeTieron the first launch (previously it spent launch #1 in a degenerate zero-day trial), anddeactivate()now drops to.freeTierrather than.expired. The SDK also sends an anonymous, automatic keyless heartbeat so the dashboard can show a trials started → converted / in free tier / expired funnel — nothing to call yourself. Most apps update with no code changes; if your product has the free tier enabled, make sure you passfreeTierEnabled: trueto the factory. Binary live via keylight-swift 0.7.0; see the Swift SDK install guide. - Swift SDK 0.6.0 — device-bound encrypted storage, no Keychain popup. License state now lives in a device-bound encrypted file by default and the Keychain is left untouched, so macOS no longer shows a Keychain permission prompt on first launch. Existing on-disk state migrates automatically and popup-free — no re-activation. This is the new default with no code change required. Binary via keylight-swift 0.6.0; see Storage.
0.14.1
Section titled “0.14.1”2026-05-30
- Free upgrades. Set an upgrade-enabled key type’s price to
0to offer it as a no-cost upgrade. Customers get it applied instantly in the portal — no payment provider required. Subscription-billed tiers are excluded.
0.14.0
Section titled “0.14.0”2026-05-29
- Customer portal. Your customers get a hosted account portal at portal.keylight.dev. They sign in with a magic link sent to the email on their license — no password — then see every license they own across your apps, copy keys, manage devices, and re-send themselves a key. Link your support pages straight to it. (A fully brand-customized portal on your own domain is on the roadmap.)
- Upgrade in the portal. Signed-in customers upgrade from their license detail: pick a higher tier and go straight to your payment provider’s checkout — no license-key or email re-entry. See Upgrades.
- Claim a legacy key. Moving customers over from a previous vendor? They prove ownership of an old key at
portal.keylight.dev/p/<tenant>/claim/<product>and Keylight mints them a license, delivered by a white-label email. Verification runs against a Keylight-managed table or your own signed webhook. Enabled per product by Keylight — contact us to turn it on for a migration. - One customer host + retirements. All public customer pages now live on
portal.keylight.dev. The standalone upgrade form (/p/<tenant>/upgrade/<product>) and the key-recovery page (/p/<tenant>/recover) are retired — customers sign in to upgrade or to recover a lost key (the portal shows their keys directly). Update any links that pointed atapi.keylight.dev/p/.... - Swift SDK 0.5.0 (breaking).
LicenseManager.upgradeURL(to:)andmakeUpgradeURL(...)are removed — they built a link to the now-retired hosted upgrade page. Point your in-app “Upgrade” button at portal.keylight.dev instead. Binary live via keylight-swift 0.5.0; see the Swift SDK install guide.
0.13.1
Section titled “0.13.1”2026-05-27
- Onboarding redesign. Setup flow rebuilt around a single code-block component. The SDK install step now points to the docs rather than dumping a long Swift snippet into the wizard, and step 5 is a payments picker covering Stripe Connect, manual Stripe, and every MoR provider with per-provider setup links.
- Pricing FAQ corrected. “Do you take a cut?” now reflects reality: 0.1% only on Stripe Connect autopilot (we run the Stripe pipeline for you); 0% on your own Stripe account or any other provider.
- Swift SDK 0.4.0 shipped. Binary is live on SwiftPM via keylight-swift 0.4.1 — defensive-readiness audit + lifecycle event notifications (
LicenseLifecycleEvent). Manifest’s platform list narrowed to macOS + iOS to match the shipped slices. See the Swift SDK install guide.
0.13.0
Section titled “0.13.0”2026-05-24
- License upgrade portal. Customers can now upgrade their license without you needing a backend. Two new Keylight-hosted endpoints:
/p/<tenant>/upgrade/<product>— public form where the customer enters their email + license key and is redirected to your payment provider’s checkout with the upgrade metadata baked in. See Upgrades./p/<tenant>/recover— emails the customer their license keys when they enter their address. Self-service support for “I lost my key.”
- Swift SDK helper.
licenseManager.upgradeURL(to: "pro")returns the pre-filled portal URL for the in-app upgrade button. - Multi-provider coverage. Stripe Connect, Stripe override key, Stripe Payment Link, Polar, Paddle, Lemon Squeezy, Creem, Gumroad, and Shopify are all supported. For non-Stripe providers, paste the hosted checkout URL into the new Upgrade checkout URL field on each key type in the dashboard.
- Paddle email enrichment. Paddle webhooks don’t include customer email; Keylight now fetches it via Paddle’s Customers API at issuance time, and caches the result. Add your Paddle API key on the Integrations card. A backfill script is available for existing Paddle customers — contact support.
- Audit additions. Two new audit-log actions surface in the Activity tab:
license_recovery_email_sentandupgrade_attempt_rejected.
0.12.0
Section titled “0.12.0”2026-05-23
- SDK traffic defense. Layered server-side protection now runs in front of every SDK endpoint. Per-route rate limits (
/validate120/240,/activate5/10,/deactivate5/10,/free-tier10/20 — steady/burst per IP per minute), a tenant-wide ceiling across all SDK routes, a per-(license, instance) cap that stops replay loops, and a/free-tiernew-instance creation cap. 429 responses now carryX-Keylight-Limit-Scope,X-Keylight-Limit,X-Keylight-Remaining, andRetry-Afterheaders so well-behaved clients can back off correctly. - Diagnostics surface on the Activity tab. A filter chip switches between Business events, SDK traffic, and All. With SDK traffic visible you get a 30-minute summary card (route × status code), a recent SDK request log, and a Manage blocks & rate limits panel where you can block by IP or by license-key hash and clear stuck rate-limit counters. Per-row Block IP / Block license buttons on each request row.
- Anomaly emails. An hourly check looks at 5 metrics (failed-activate spike, new-free-tier spike, rate-limit spike, sustained
401s, single-IP dominance) against a rolling baseline. Unusual traffic emails you and shows a dismissible badge on the Activity tab. - SDK key rotation is now support-gated. Rotation goes through Keylight support to prevent accidental self-lockout. The dashboard rotate button is removed; existing keys keep working unchanged. See SDK key.
0.11.2
Section titled “0.11.2”2026-05-27
- Swift SDK 0.4.0 — defensive-readiness hardening + lifecycle event notifications. Two things landed in one cut:
- 14-finding audit patch. Tighter signature verification, stricter activation-token handling, network-error retry semantics, and clearer error logging. The factory grew three optional parameters (
kid:,freeTierEnabled:,maxOfflineDays:) with defaults that preserve prior behavior. Drop-in update for apps that don’t opt in. If your product has the keyless free tier enabled, you must passfreeTierEnabled: trueto the factory — without it, post-trial users resolve to.expiredinstead of.freeTier. - Lifecycle event notifications. A new opt-in
keylightLifecycleEventnotification with three derived events (.expired,.restored,.renewed). Subscribe viaNotificationCenterto dismiss paywalls on renewal, show “thanks for renewing” toasts on expiry advance, or react to lapses — without pollingLicenseManager.licenseState. Suppressed on first-launch and user-initiateddeactivate(). Stale entitlements are now cleared on.expired/.invalid/.freeTier. See the Swift SDK install guide for the subscriber example.
- 14-finding audit patch. Tighter signature verification, stricter activation-token handling, network-error retry semantics, and clearer error logging. The factory grew three optional parameters (
0.11.1
Section titled “0.11.1”2026-05-21
- Free-tier devices on the Analytics page. Per-app cards now surface active free-tier device counts alongside paying instances, so the conversion shape is visible at a glance. New icon for the Free-tier Devices stat.
0.11.0
Section titled “0.11.0”2026-05-19
- Multi-provider payments. Keylight is no longer Stripe-only. Each app can now run on a Merchant-of-Record provider: Polar, Paddle, Lemon Squeezy, Shopify, Creem, or Gumroad — alongside the existing Stripe Connect path. Each adapter handles the platform’s webhook payloads, license issuance, customer linking, and (where supported) subscription lifecycle + refunds.
- Per-app payment providers. The Integrations card on the dashboard scopes payment providers to individual apps. Different apps under the same account can be on different providers (one on Stripe Connect, another on Polar, a third on Shopify). The Activity log surfaces the source per issued license.
- Payments Control Center. Consolidates Stripe Connect setup, pricing, manual key issuance, and the MoR provider roster into one card per app on the App view. Replaces several scattered pages and the standalone
/stripe-pricingroute.
0.10.0
Section titled “0.10.0”2026-05-18
- Subscription-type license keys. Key types can now be set to
billingModel: 'subscription'. Expiry is driven by the payment platform (Stripecustomer.subscription.updated) rather than bydurationDays. A configurablegraceDayswindow (default 7) keeps the license active after a failed renewal while payment recovers. See Subscription key types. - Limited access fallback (
.limitedstate). Key types withfallbackAccess: trueissue a valid signed lease on expiry rather than refusing. The lease carries no paid entitlements and the Swift SDK reportsLicenseState.limited. Your app can keep a reduced mode (e.g. read-only, data export) so customers never lose access to their data. See Limited access. - License upgrades. A checkout with
keylight_upgrade_keymetadata performs an in-place key-type swap on the existing license — same key string, no remint. Entitlements and activation limit update to the new tier; the device sees the change on its next lease fetch (or immediately viarefresh(force: true)on return from a Keylight checkout). - Keyless free tier (
.freeTierstate). Apps can setfreeTierEnabledon their product config to offer a free plan to users who never paid. When the trial is ended/not-started andfreeTierEnabledis on, the SDK reportsLicenseState.freeTierinstead ofLicenseState.expired. The SDK tracks an anonymous instance UUID (no PII) and sends a lightweight heartbeat to Keylight so the dashboard shows free-tier device counts alongside paying customers. Free→paid conversions are automatically linked at activation time. - New
refresh(force: true)API. Bypasses the 5-minute debounce and 6-hour staleness gate. Called automatically when the app foregrounds after returning from a Keylight checkout deep link, so upgrade entitlements appear without an extra relaunch.
- Stripe and Gumroad license issuance now appears in the Activity log. Each webhook-driven mint writes a
mint_licenseaudit entry with the source, app, and key type, so the Activity feed shows lines likeStripe · MyApp · Pro licenseinstead of leaving sales invisible. - Per-payment detail view at
/dashboard/<tenant>/billing/<paymentId>. Click any Revenue row to see the customer, app, key type (display name + activation limit + duration), and the linked license (display key, status, expiry, active instances) in one place. - Revenue table now has an App column — surfacing the Keylight-configured product display name (joined via the issued license) instead of the raw Stripe/Gumroad line-item description.
- Gumroad parity for customer linking. The Gumroad webhook now upserts the buyer into the
customerstable and back-links the issued license to that customer, mirroring what Stripe has done since 0.9.0. Gumroad buyers now appear in the dashboard’s Customers view, and “licenses owned by this customer” joins resolve correctly.
- Owner notifications on license events. You get an email when a license is issued and when an instance activates against one of your licenses.
product_idsurfaced on the app view with a copy button — needed for raw-API integration testing.- Stripe customer linked as license owner on every checkout (not just first-time buyers), so the dashboard’s customer view stays correct across repeat purchases.
- Argon2id is now the default password hash (PBKDF2 hashes still verify and rehash on next login).
- Webhook now tries every product override when Stripe metadata is missing.
- Unified pill UI across dashboard, marketing, and operator surfaces.
- Design CSS externalized to
/static/csswith immutable cache; Google Fonts viapreconnectinstead of inline@import.
- Hardened authentication. Login, signup, password reset, and session management moved off the bespoke handler onto a battle-tested authentication library. Cookies remain backwards-compatible.
- Account ID picker on signup. You now choose your account ID directly on the signup form (between email and password) — see Signup & verification.
- Pricing and landing-page redesign.
- Dashboard lineage column for remint chains; license status icon moved to the left of list rows.
- Remint inherits predecessor
expiresAtinstead of resetting the clock.
maxOfflineDays(default 15) onKeylightConfiguration. The SDK recordslastValidatedOnlineafter every successfulactivate/validateand rejects cached leases past this window — a hard backstop on top of the 7-day lease TTL. See Offline leases.- Remint chain linking. Reminted licenses now carry a
previousKeylink so the dashboard can render the full chain. - License expiry tracking in the SDK.
LicenseManagerexposes the current lease’sexpiresAt. - Marketing pages: support, privacy, terms.
- SDK
productionOriginupdated toapi.keylight.devto match the rebrand.
- Storage migration. Tenants, licenses, customers, payments, and the audit log moved to a new storage layer, for headroom as accounts grow.
- Rebrand to
keylight.dev— Keylight now servesapp.keylight.dev(dashboard) +api.keylight.dev(SDK/API). - Stripe test-mode UI polish with manual-webhook detection.
- Stripe test mode per product. Each product can hold an encrypted Stripe override (secret key + webhook secret) that takes effect when the dashboard’s “Test mode” toggle is on. See Test mode.
- Test-mode UI in the dashboard — enable/exit button, credential form, “Testing” badge.
- License key delivery email sent automatically on Stripe checkout completion and Gumroad purchase.
supportEmailonProductConfig— collected during onboarding, used as the reply-to / footer contact in customer-facing emails.
- Full-platform xcframework via CI: tvOS, watchOS, and visionOS in addition to iOS and macOS.
Keylight.manager(...)one-call factory.X-Keylight-SDK-Keyheader now attached to every API request and validated server-side. See SDK key.- SwiftPM product renamed from
KeylighttoKeylightSDK.
- Self-service account signup with Turnstile and email verification.
- Operator admin portal at
/admin. - Stripe Billing integration: paid plan checkout, subscription lifecycle webhooks.
- 6-state account lifecycle machine:
pending_verification → trial → active → past_due → canceled → suspended. - Hard instance cap enforcement at
/activate; soft license/API metering with 90% warning emails. - Argon2id password hashing with dual-verify PBKDF2 migration.
- Licensing foundation: Swift SDK, Keylight API, Ed25519 lease signing, Stripe webhook license issuance, per-account dashboard.