Product License Activation
LaunchKit Pro includes an optional product license key foundation for buyers who sell templates, self-hosted kits, or SaaS products and want activation flows without wiring Payhip, Gumroad, or Lemon Squeezy on day one.
Overview
| Topic | Default behavior |
|---|---|
| Enforcement | Disabled (LICENSE_ENFORCEMENT_ENABLED=false) |
| Provider | MOCK — admin-generated keys only |
| App access | Works without activation |
| Full license key | Shown once when admin creates a key |
| Stored in database | SHA-256 hash + preview (LKP-****-****-****-ABCD) |
When enforcement is disabled, users can still activate keys for testing. The dashboard shows “License enforcement disabled” or “Optional license foundation” messaging.
Environment variables
Add to backend .env (see .env.example):
LICENSE_ENFORCEMENT_ENABLED=false
LICENSE_PROVIDER=MOCK
LICENSE_KEY_PREFIX=LKP
LICENSE_MAX_ACTIVATIONS_DEFAULT=3
LICENSE_ALLOW_OFFLINE_MODE=true
LICENSE_SUPPORT_EMAIL=support@launchkitlabs.com| Variable | Purpose |
|---|---|
LICENSE_ENFORCEMENT_ENABLED | When false, the app never blocks access for missing licenses |
LICENSE_PROVIDER | MOCK today; extend for Payhip/Gumroad/Lemon Squeezy later |
LICENSE_KEY_PREFIX | Prefix for generated keys (default LKP) |
LICENSE_MAX_ACTIVATIONS_DEFAULT | Default seat/instance limit for new keys |
LICENSE_ALLOW_OFFLINE_MODE | Reserved for future offline verification flows |
LICENSE_SUPPORT_EMAIL | Shown in buyer-customizable support messaging |
License key format
Keys are generated as:
LKP-XXXX-XXXX-XXXX-XXXXCharacters exclude easily confused 0/O and 1/I. The full key is hashed before storage; only a preview is retained for UI and audit logs.
Important:
- License keys must be generated by an admin (or imported through a future marketplace provider integration).
- A manually typed key with the correct format is still invalid unless it exists in the database.
- The admin list shows previews only — never full keys after creation.
- Full keys are shown once immediately after admin creation.
- Duplicate key hashes are prevented by a unique database constraint on
keyHash.
Validation messages
| Situation | Message |
|---|---|
| Bad format | Invalid license key format. Expected format: LKP-XXXX-XXXX-XXXX-XXXX. |
| Valid format, not in DB | License key not found. Please use a key generated by the admin license system. |
| Revoked | License key is revoked. |
| Expired | License key is expired. |
| Activation limit | Activation limit reached. |
| Same instance re-activate | Idempotent — does not increment activation count again |
Input is normalized before validation: trimmed, uppercased, whitespace/newlines removed, and hyphens standardized when safe.
Editions
Each license maps to a product edition:
| Edition key | Display name |
|---|---|
LITE | Lite Edition |
PRO | Pro Edition |
ENTERPRISE | Enterprise Edition |
On activation, workspace settings.license records the edition. Billing subscription edition still takes precedence in the UI when both billing and license exist — mock checkout behavior is unchanged.
User flows
Buyer / workspace user
Dashboard → License (/dashboard/license):
- View current status, edition, preview, enforcement flag
- Activate, verify, or deactivate a key
- Friendly errors for invalid, revoked, expired, or limit-reached keys
Settings also shows a summary card linking to the license page.
Admin
Admin → Licenses (/admin/licenses):
- Paginated list of all license keys (newest first, previews only)
- Create keys (full key returned once in a separate success panel — list is refreshed, not replaced)
- Filter by status, edition, source
- View activations
- Revoke keys
- Duplicate rows are deduplicated client-side; duplicate key hashes are blocked server-side
Demo users can view and try activation where allowed, but cannot create or revoke admin licenses (demo guard).
API endpoints
Authenticated user
| Method | Path | Description |
|---|---|---|
GET | /license/status?workspaceId= | Current license status |
POST | /license/activate | Activate a key |
POST | /license/verify?workspaceId= | Re-check active license |
POST | /license/deactivate?workspaceId= | Deactivate current activation |
Admin
| Method | Path | Description |
|---|---|---|
GET | /admin/licenses | Paginated list |
POST | /admin/licenses | Create key (full key once) |
GET | /admin/licenses/:licenseId | Key details |
PATCH | /admin/licenses/:licenseId | Update status, edition, limits, expiry |
POST | /admin/licenses/:licenseId/revoke | Revoke key |
GET | /admin/licenses/:licenseId/activations | List activations |
Permissions: admin.licenses.view, admin.licenses.manage.
Audit events
Audit logs record (never the full key):
license.createdlicense.updatedlicense.activatedlicense.verifiedlicense.deactivatedlicense.revokedlicense.activation_failed
Customization for buyers
This module is a foundation:
- Set
LICENSE_ENFORCEMENT_ENABLED=truewhen you are ready to gate features. - Swap
LICENSE_PROVIDERand add provider adapters alongside the mock service. - Connect marketplace webhooks to auto-create keys with
source=PAYHIP/GUMROAD/LEMON_SQUEEZY. - Adjust edition mapping or workspace settings merge logic in
LicenseService.
Update access (optional)
When you enable license enforcement later, the Product Updates page (/dashboard/updates) can reflect whether update access requires an active license. With enforcement disabled (default), update status and changelog remain available as an optional foundation.
Configure installed version with LAUNCHKIT_VERSION and connect secure downloads through UPDATE_PROVIDER when ready. See Product Updates.
Manual test checklist
- Admin creates a Pro license → full key shown once.
- User activates → status active, preview only in UI.
- Verify and deactivate work.
- Admin revoke → user verify shows revoked.
- Invalid key → friendly error.
- Activation limit enforced when max seats reached.
- Demo user blocked from admin license creation.
- Billing, AI, dashboard, and settings pages still work with enforcement off.