A Desktop Product with Fail-Closed Licensing
BizXtract: a sole-authored cross-platform desktop app and its licensing platform: Ed25519-signed device-bound entitlements, offline grace, idempotent billing webhooks, and crash-safe local storage.
- Role:
- Sole engineer: desktop client, license server, payments, CI/CD, release pipeline
- Period:
- 2026, independent product
- .NET 10
- Avalonia
- Node.js
- MongoDB
- Stripe
- Paddle
- Ed25519
- SQLite
- Velopack
Context
BizXtract is a lead-generation desktop product I author and ship solo: a .NET 10 + Avalonia rewrite of an older WinForms tool, sold under subscription. Selling software to strangers changes your engineering priorities: licensing, updates, and crash recovery stop being infrastructure and become the product.
Architecture
Key decisions
Fail closed, but humanely. The client verifies an Ed25519-signed entitlement before running and refuses anything unsigned or expired. Entitlements bind to an installation-scoped device ID and re-verify server-side on every call. Because customers work offline, a 3-day grace window keeps them productive past the last sync, with revoked and expired kept as distinct states so support can tell an ex-customer from an airplane passenger.
Billing webhooks are exactly-once by design. The Stripe-to-Paddle migration forced idempotent webhook processing: every event handler is safe to replay, so a duplicated or out-of-order delivery cannot double-activate or wrongly revoke a license.
Updates must be reversible. Velopack ships delta updates from a CDN with one-line rollback. Combined with SQLite in WAL mode and crash-recovery tests in CI, a bad release or a mid-write power loss degrades to an inconvenience instead of data loss.
Results
A single codebase ships to Windows and macOS with signed releases, auto-updates, subscription validation, and smoke tests in CI. The licensing platform has survived a live payment-provider migration without locking out a single paying user.
What I'd do differently
Design the entitlement schema for multiple products from the start. The server assumes one product in several places, and generalizing it now costs more than it would have on day one.