Keysmith is a modern, open-source licensing server that provides secure license validation with offline verification capabilities, entitlements management, and seamless integration with billing providers like Stripe and Polar.
- Secure License Generation: Ed25519-based license signing with JWT tokens
- Offline Verification: Client libraries can validate licenses without internet connectivity
- Device Management: Activation limits and device tracking
- Entitlements System: Flexible feature flags and usage limits
- Webhook Integration: Automated license lifecycle via Stripe/Polar webhooks
- Automated License Management: Cron jobs for expiry and email reminders
- Customer Self-Service Portal: Magic link authentication with device management
- Admin Interface: Complete web UI for license, product, and template management
- Software Updates: Versioned release management with channel support
- RESTful API: Simple, well-documented REST endpoints
- Type-Safe: Built with TypeScript for end-to-end type safety
Built on Encore.ts with:
- Backend: TypeScript, PostgreSQL, Pub/Sub, Cron Jobs
- Crypto: Ed25519 signatures for maximum security and performance
- Database: PostgreSQL with optimized schemas and indexes
- Deployment: Cloud-native with built-in scaling and monitoring
- Encore CLI installed
- Node.js 18+
- Clone and setup:
git clone <your-repo>
cd keysmith
encore app create keysmith- Start development server:
encore run- Run the automated setup:
chmod +x examples/curl/setup-example.sh
./examples/curl/setup-example.shThis will:
- Generate Ed25519 key pairs
- Set the required secrets
- Seed demo data
- Test the basic functionality
- Generate Ed25519 keys:
# Start the development server first
encore run
# Generate a key pair
curl -X POST http://localhost:4000/v1/setup/generate-key- Set the private key secret:
# Copy the private key from the previous response
encore secret set --type dev Ed25519PrivateKey your-private-key-here- Seed demo data:
# Option 1: Use the built-in seed endpoint (recommended)
curl -X POST http://localhost:4000/v1/setup/seed
# Option 2: Use the comprehensive demo data script
chmod +x create-demo-licenses.sh
./create-demo-licenses.sh
# Option 3: Add additional demo data with different license statuses
chmod +x create-more-demo-data.sh
./create-more-demo-data.sh- Test the API:
chmod +x examples/curl/api-examples.sh
./examples/curl/api-examples.shKeysmith includes comprehensive demo data to help you explore all features immediately after setup:
# Seeds products, customers, templates, and Stripe mappings
curl -X POST http://localhost:4000/v1/setup/seed# Creates varied demo licenses with realistic data
chmod +x create-demo-licenses.sh
./create-demo-licenses.sh# Adds licenses with all statuses: active, expired, suspended, revoked
chmod +x create-more-demo-data.sh
./create-more-demo-data.shAfter seeding, you'll have:
- DevTool Pro - Professional development tools (
devtool-pro) - API Gateway Plus - Advanced API management (
api-gateway-plus)
- Alice Smith (
alice@example.com) - Pro user - Bob Johnson (
bob@company.com) - Team user - Charlie Brown (
charlie@startup.io) - Trial user - Demo User (
demo@keysmith.dev) - Long-term demo - Various status-specific test customers
- Active licenses with different expiration dates (30 days, 90 days, 1 year)
- Trial license (7 days) for testing short-term scenarios
- Expired license for testing expiration handling
- Suspended license for payment failure simulation
- Revoked license for policy violation testing
- Various activation limits (1, 3, 5, 10 devices)
// DevTool Pro Template
{
"features": {
"code_analysis": true,
"team_collaboration": true,
"advanced_debugging": true,
"custom_integrations": false
},
"limits": {
"projects": 10,
"team_members": 5,
"api_calls_per_month": 50000,
"storage_gb": 10
},
"ui": {
"theme_customization": true,
"white_labeling": false
}
}
// API Gateway Template
{
"features": {
"request_routing": true,
"rate_limiting": true,
"analytics": true,
"custom_middleware": true,
"webhook_support": true
},
"limits": {
"requests_per_second": 1000,
"endpoints": 50,
"webhook_destinations": 10
},
"monitoring": {
"real_time_metrics": true,
"alerting": true,
"log_retention_days": 30
}
}- Monthly and yearly billing cycles
- Different activation limits per plan
- Realistic license durations (1 month, 12 months)
With demo data loaded, you can immediately explore the admin interface:
-
Start the frontend (if not already running):
cd frontend bun install && bun dev
-
Access the admin panel: http://localhost:5173/admin
-
Login: Use admin key
test-admin-key-for-mvp -
Explore features:
- Dashboard: View license statistics and recent activity
- Licenses Tab: Browse, search, filter, and manage all licenses
- Templates Tab: View and edit entitlement templates
- Create License: Test the full license creation workflow
- Edit Licenses: Update status, expiration, limits, metadata
# View all products
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/products
# List all licenses (paginated)
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/licenses
# Filter active licenses only
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/licenses?status=active"
# Search licenses by customer email
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/licenses?search=alice"
# View entitlement templates
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/entitlements/templates
# Check recent events
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/events# Test online license validation
LICENSE_KEY="UQX5-DU7V-G7KV-NSPM" # Alice's license from demo data
curl "http://localhost:4000/v1/validate?license_key=$LICENSE_KEY&device_id=test-device-001"
# Test device activation
curl -X POST http://localhost:4000/v1/activate \
-H "Content-Type: application/json" \
-d '{
"license_key": "'$LICENSE_KEY'",
"device_id": "test-laptop-001",
"device_name": "Development Laptop"
}'
# Get entitlements for a license
curl "http://localhost:4000/v1/entitlements/$LICENSE_KEY"You can also create your own demo data:
# Create a custom license
curl -X POST http://localhost:4000/v1/admin/licenses \
-H "X-Admin-Key: test-admin-key-for-mvp" \
-H "Content-Type: application/json" \
-d '{
"product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
"customer_email": "yourname@company.com",
"customer_name": "Your Name",
"expires_at": "2025-12-31T23:59:59Z",
"max_activations": 5,
"metadata": {"plan": "enterprise", "custom": true}
}'Keysmith includes a self-service customer portal where your users can view their licenses and manage device activations.
-
Start the frontend server:
cd frontend bun dev -
Navigate to the portal: Open your browser to http://localhost:5173/portal
The portal uses secure, passwordless magic links for authentication:
- A customer enters their email address.
- A unique, short-lived login link is sent to their email.
- Note: In local development, if you haven't configured email sending with Resend, the magic link will be printed in your backend terminal (where
encore runis active).
- Note: In local development, if you haven't configured email sending with Resend, the magic link will be printed in your backend terminal (where
- Clicking the link authenticates the user and creates a secure session.
- View all licenses associated with their email address.
- See license status, expiration dates, and activation limits.
- Download the latest version of their purchased software.
- (Coming soon) Manage device activations for each license.
- Use a demo customer email: After running the seed script, use an email like
alice@example.com. - Request a magic link from the portal login page.
- Check your backend terminal for the logged magic link URL.
- Copy and paste the URL into your browser to log in.
Keysmith includes a robust cron job system that automatically manages the license lifecycle, ensuring your customers are always informed about their license status.
- Schedule: Every 6 hours (00:00, 06:00, 12:00, 18:00 UTC)
- Function: Automatically transitions expired licenses to 'expired' status
- Benefits: Ensures expired licenses are immediately deactivated
- Schedule: Daily at 09:00 UTC
- Function: Sends reminder emails to customers whose licenses expire within 7 days
- Email Templates: Professional HTML emails with license details and renewal links
During development, you can manually trigger the cron jobs to test their functionality:
# Manually process expired licenses
curl -X POST http://localhost:4000/v1/cron/process-expired
# Manually send expiry reminder emails
curl -X POST http://localhost:4000/v1/cron/send-expiry-reminders
# Check recent cron job results in events
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=cron"To enable automated email reminders in production, configure your Resend integration:
-
Set Resend secrets:
encore secret set --type production ResendApiKey your-resend-api-key encore secret set --type production ResendDomain your-verified-domain.com
-
Test email functionality:
# Test if email can be sent (check your terminal for email content in dev mode) curl -X POST http://localhost:4000/v1/cron/send-expiry-reminders
To test the cron functionality, create licenses that will expire soon:
# Create a license expiring in 3 days (perfect for testing reminders)
TOMORROW=$(date -d "+3 days" -u +"%Y-%m-%dT%H:%M:%SZ")
curl -X POST http://localhost:4000/v1/admin/licenses \
-H "X-Admin-Key: test-admin-key-for-mvp" \
-H "Content-Type: application/json" \
-d '{
"product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
"customer_email": "test-expiring@example.com",
"customer_name": "Test Expiring User",
"expires_at": "'$TOMORROW'",
"max_activations": 1,
"metadata": {"test": "expiry_reminder"}
}'
# Create an already expired license (for testing expired processing)
YESTERDAY=$(date -d "-1 day" -u +"%Y-%m-%dT%H:%M:%SZ")
curl -X POST http://localhost:4000/v1/admin/licenses \
-H "X-Admin-Key: test-admin-key-for-mvp" \
-H "Content-Type: application/json" \
-d '{
"product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
"customer_email": "test-expired@example.com",
"customer_name": "Test Expired User",
"expires_at": "'$YESTERDAY'",
"max_activations": 1,
"metadata": {"test": "already_expired"}
}'Cron job activities are logged in the events system:
# View recent cron job activities
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=cron"
# View licenses updated by expiry processing
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=expired"
# Check for email-related events
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=reminder"The expiry reminder emails use professional HTML templates. You can customize them by modifying the email templates in the cron service:
- Subject: "Your [Product Name] license expires in [X] days"
- Content: HTML email with license details, expiration date, and renewal call-to-action
- Styling: Professional layout with your branding
# Generate keys (if you haven't already)
curl -X POST http://localhost:4000/v1/setup/generate-key
# Set the private key for production (use the key from above)
encore secret set --type production Ed25519PrivateKey YOUR_PRIVATE_KEY_HERE
# Set Stripe webhook secret for production (get from Stripe dashboard)
encore secret set --type production StripeWebhookSecret YOUR_STRIPE_WEBHOOK_SECRET_HERE
# Set a secret for signing magic links
encore secret set --type production MagicLinkSecret YOUR_RANDOM_SECRET_STRING_HERE
# Set Resend API key for sending emails
encore secret set --type production ResendApiKey YOUR_RESEND_API_KEY_HERE
# Set Resend domain for sending emails
encore secret set --type production ResendDomain your-verified-domain.com# Deploy to production
encore deploy --env=production
# Or deploy to staging first
encore deploy --env=staging- Go to your app in the Encore Cloud Dashboard
- Navigate to the "Infrastructure" tab
- Click on "Secrets"
- Add
Ed25519PrivateKeywith your private key value - Add
StripeWebhookSecretwith your Stripe webhook secret - Add
MagicLinkSecretwith a random secure string - Add
ResendApiKeywith your key from Resend - Add
ResendDomainwith your verified domain from Resend
POST /v1/licenses- Create a new licenseGET /v1/validate?license_key=XXX- Validate license onlinePOST /v1/licenses/{id}/revoke- Revoke a licenseGET /v1/public_key- Get public key for offline verification
POST /v1/activate- Activate a devicePOST /v1/deactivate- Deactivate a device
GET /v1/entitlements/{license_key}- Get signed entitlements
POST /webhooks/stripe- Stripe webhook handler
GET /v1/admin/dashboard- Get dashboard statisticsGET /v1/admin/licenses- List/search/filter licenses (paginated)POST /v1/admin/licenses- Create new license manuallyPUT /v1/admin/licenses/:id- Update existing licenseGET /v1/admin/products- List all productsGET /v1/admin/entitlements/templates- List entitlement templatesPOST /v1/admin/entitlements/templates- Create new templatePUT /v1/admin/entitlements/templates/:id- Update existing templateGET /v1/admin/events- List system events (audit trail)
POST /v1/setup/generate-key- Generate Ed25519 key pairPOST /v1/setup/seed- Seed database with demo data
POST /v1/cron/process-expired- Manually trigger expired license processingPOST /v1/cron/send-expiry-reminders- Manually trigger expiry reminder emails
import { LicenseVerifier } from './examples/typescript/verify-license';
// Get public key from your server
const response = await fetch('https://your-server.com/v1/public_key');
const { public_key } = await response.json();
// Initialize verifier
const verifier = new LicenseVerifier(public_key);
// Verify license offline
try {
const license = await verifier.verifyLicense(licenseToken);
console.log('License is valid:', license);
// Check entitlements
const entitlements = await verifier.verifyEntitlements(entitlementsToken);
const hasFeature = verifier.hasFeature(entitlements.entitlements, 'features.advanced_mode');
const apiLimit = verifier.getLimit(entitlements.entitlements, 'limits.api_calls');
} catch (error) {
console.error('License verification failed:', error);
}// Validate license with server
const response = await fetch(`https://your-server.com/v1/validate?license_key=${licenseKey}&device_id=${deviceId}`);
const validation = await response.json();
if (validation.valid) {
console.log('License is valid');
console.log('Activations remaining:', validation.activations_remaining);
} else {
console.log('License invalid:', validation.reason);
}- Ed25519 Signatures: Modern, fast, and secure cryptographic signatures
- JWT Tokens: Industry-standard token format with built-in expiration
- Webhook Verification: Secure webhook signature validation
- Rate Limiting: Built-in protection against abuse
- Audit Logging: Complete event trail for license operations
This error means you haven't set the required secret for your deployment environment:
-
Generate a key pair (if you haven't already):
curl -X POST http://localhost:4000/v1/setup/generate-key
-
Set the secret for your target environment:
# For production encore secret set --type production Ed25519PrivateKey YOUR_PRIVATE_KEY # For staging encore secret set --type staging Ed25519PrivateKey YOUR_PRIVATE_KEY
-
Verify the secret is set:
encore secret list
-
Deploy again:
encore deploy --env=production
- Key generation fails: Ensure
@noble/ed25519dependency is installed - API calls fail: Check that the server is running and secrets are set
- Database connection issues: Ensure PostgreSQL is available in your deployment environment
The system uses PostgreSQL with the following main tables:
- products - Your software products
- customers - License holders
- licenses - Individual licenses with status and limits
- activations - Device activations and tracking
- entitlement_templates - Feature and limit definitions
- events - Audit log of all license operations
- versions - Software update metadata
- stripe_product_mappings - Maps Stripe products to Keysmith products
- failed_webhook_events - Failed webhook events for manual intervention
- Configure your Stripe webhook endpoint to point to
/webhooks/stripe:
{
"events": [
"customer.subscription.created",
"customer.subscription.updated",
"customer.subscription.deleted",
"invoice.payment_succeeded",
"invoice.payment_failed"
],
"url": "https://your-keysmith-server.com/webhooks/stripe"
}- Set up product mappings in your database:
INSERT INTO stripe_product_mappings (
stripe_product_id,
stripe_price_id,
keysmith_product_id,
license_duration_months,
max_activations
) VALUES (
'prod_stripe_id_here',
'price_stripe_id_here',
'your-keysmith-product-uuid',
12, -- 12 months license duration
5 -- 5 device activations
);- Test webhook integration:
chmod +x examples/webhooks/test-webhook.sh
./examples/webhooks/test-webhook.shcustomer.subscription.createdβ Creates new licenseinvoice.payment_succeededβ Renews/extends licenseinvoice.payment_failedβ Suspends licensecustomer.subscription.deletedβ Revokes license
Failed webhook events are automatically stored in failed_webhook_events table for manual review:
- Retryable errors: Temporary issues (DB connection, etc.) - auto-retry
- Manual errors: Business logic issues - require human intervention
- Permanent errors: Invalid event types - logged and ignored
# Run all tests
encore test
# Run with watch mode
npm run test:watch
# Run with coverage
npm run test:coverage
# Run specific test file
npx vitest backend/licensing/crypto.test.ts- Unit Tests: Individual function and component testing
- Integration Tests: Full API endpoint testing with database
- Crypto Tests: Ed25519 signing/verification validation
- Activation Tests: Device limit and quota management
- Validation Tests: License status and expiration logic
- Webhook Tests: Stripe event processing and signature verification
- Cron Job Tests: Automated license expiry and email reminder testing
- Email Tests: Email sending and template validation (using test mode)
# Test expired license processing
npm run test -- --grep "expired license"
# Test email reminder functionality
npm run test -- --grep "expiry reminder"
# Test cron job scheduling and execution
npm run test -- --grep "cron"
# Run all cron-related tests
npm run test -- backend/cron/Check the examples/ directory for:
- TypeScript offline verification client
- curl API examples
- Setup automation scripts
- Webhook testing scripts and sample payloads
- Integration examples for popular languages
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass:
npm test - Submit a pull request
Apache License 2.0 - see LICENSE file for details
Keysmith - Secure, scalable, and simple license management for modern software teams.