Skip to content

Changelog

2026-07-07

  • Rust SDK — show your app version in the dashboard. The Rust SDK attaches sdk_version and platform automatically, 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 in keylight 0.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.

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.

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).

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. Install keylight (crates.io).

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.

2026-06-07

  • Swift SDK 0.8.0 — choose your storage backend from the factory. Keylight.manager(...) now takes an optional storage: parameter, so the 0.6.0 storage options are reachable in one line: storage: .encryptedFile(keychainMirror: true) to also keep a Keychain recovery copy, or storage: .keychain for 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.

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.

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 .freeTier on the first launch (previously it spent launch #1 in a degenerate zero-day trial), and deactivate() now drops to .freeTier rather 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 pass freeTierEnabled: true to 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.

2026-05-30

  • Free upgrades. Set an upgrade-enabled key type’s price to 0 to offer it as a no-cost upgrade. Customers get it applied instantly in the portal — no payment provider required. Subscription-billed tiers are excluded.

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 at api.keylight.dev/p/....
  • Swift SDK 0.5.0 (breaking). LicenseManager.upgradeURL(to:) and makeUpgradeURL(...) 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.

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.

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 in KV). 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_sent and upgrade_attempt_rejected.

2026-05-23

  • SDK traffic defense. Layered server-side protection now runs in front of every SDK endpoint. Per-route rate limits (/validate 120/240, /activate 5/10, /deactivate 5/10, /free-tier 10/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-tier new-instance creation cap. 429 responses now carry X-Keylight-Limit-Scope, X-Keylight-Limit, X-Keylight-Remaining, and Retry-After headers 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.

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 pass freeTierEnabled: true to the factory — without it, post-trial users resolve to .expired instead of .freeTier.
    • Lifecycle event notifications. A new opt-in keylightLifecycleEvent notification with three derived events (.expired, .restored, .renewed). Subscribe via NotificationCenter to dismiss paywalls on renewal, show “thanks for renewing” toasts on expiry advance, or react to lapses — without polling LicenseManager.licenseState. Suppressed on first-launch and user-initiated deactivate(). Stale entitlements are now cleared on .expired / .invalid / .freeTier. See the Swift SDK install guide for the subscriber example.

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.

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-pricing route.

2026-05-18

  • Subscription-type license keys. Key types can now be set to billingModel: 'subscription'. Expiry is driven by the payment platform (Stripe customer.subscription.updated) rather than by durationDays. A configurable graceDays window (default 7) keeps the license active after a failed renewal while payment recovers. See Subscription key types.
  • Limited access fallback (.limited state). Key types with fallbackAccess: true issue a valid signed lease on expiry rather than refusing. The lease carries no paid entitlements and the Swift SDK reports LicenseState.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_key metadata 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 via refresh(force: true) on return from a Keylight checkout).
  • Keyless free tier (.freeTier state). Apps can set freeTierEnabled on their product config to offer a free plan to users who never paid. When the trial is ended/not-started and freeTierEnabled is on, the SDK reports LicenseState.freeTier instead of LicenseState.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_license audit entry with the source, app, and key type, so the Activity feed shows lines like Stripe · MyApp · Pro license instead 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 customers table 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_id surfaced 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/css with immutable cache; Google Fonts via preconnect instead of inline @import.
  • Better Auth-backed authentication. Login, signup, password reset, and session management run on Better Auth instead of the bespoke handler. 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 expiresAt instead of resetting the clock.
  • maxOfflineDays (default 15) on KeylightConfiguration. The SDK records lastValidatedOnline after every successful activate/validate and 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 previousKey link so the dashboard can render the full chain.
  • License expiry tracking in the SDK. LicenseManager exposes the current lease’s expiresAt.
  • Marketing pages: support, privacy, terms.
  • SDK productionOrigin updated to api.keylight.dev to match the rebrand.
  • Postgres migration: KV → Neon + Hyperdrive + Drizzle. All entity records (tenants, licenses, customers, payments, audit log) now live in Neon Postgres, accessed via Cloudflare Hyperdrive. KV is reserved for TTL ephemera and encrypted secrets.
  • Rebrand to keylight.dev — Keylight now serves app.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.
  • supportEmail on ProductConfig — 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-Key header now attached to every API request and validated server-side. See SDK key.
  • SwiftPM product renamed from Keylight to KeylightSDK.
  • 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.