License Management Add-on
A provider-based license key management system for Larapen with API verification, domain/machine activation tracking, feature gating, and webhook integration.
Multi-Provider
Manual keys, Envato purchase codes, and external webhook sources: all in one system.
Activation Tracking
Track activations per domain, machine ID, or IP. Enforce per-key limits (e.g. 3 domains max).
Feature Flags
Define per-product feature sets (modules, limits, tiers) returned in every verification response.
REST API
Verify, activate, and deactivate endpoints for client integration.
Use Cases
SaaS Application
Your SaaS product uses self-hosted license keys to control plan tiers.
- Each plan maps to a Product with different features:
{"max_users": 5}(Starter),{"max_users": 50, "api_access": true}(Pro). - The application calls
GET /api/licenses/verifyat boot to check validity and read thefeaturesobject. - No domain activation needed: just verification.
Desktop Application
A desktop app is licensed per machine.
- On launch, the app calls
POST /api/licenses/activatewith amachine_id. - The app periodically calls
GET /api/licenses/verifyto check validity. - When the user decommissions a machine, call
POST /api/licenses/deactivateto free the slot.
WordPress Plugin / Theme
A premium WordPress plugin validates its license on the customer's site.
- Admin enters the key in WP settings. The plugin calls
POST /api/licenses/activatewithdomain = site_url(). - Verification runs daily via WP-Cron using
GET /api/licenses/verify. - If activation limit is reached, the customer must deactivate an old domain before activating a new one.
API Access Tiers
You sell API access with different rate limits per plan.
- Products define features:
{"rate_limit": 100, "endpoints": ["read"]}(Basic),{"rate_limit": 10000, "endpoints": ["read","write","admin"]}(Enterprise). - Your API gateway calls
GET /api/licenses/verifyand reads thefeaturesto enforce limits.
Envato (CodeCanyon) Products
You sell a product on CodeCanyon and want automatic license verification.
- Configure the Envato provider with your API token and author username.
- Customers enter their Envato purchase code. The system verifies it directly against the Envato API.
- No manual key creation needed: the provider handles everything.
JavaScript Library / Plugin
You sell a premium JS editor component. Each customer receives a license key valid for N domains.
- On page load, the library calls
POST /api/licenses/activatewith the key and current domain. - The server verifies the key, activates on the domain (counting against the limit), and returns feature flags via
GET /api/licenses/verify(e.g."toolbar_presets": true,"ai_integration": false).
Requirements
- Larapen CMS v1.0.0 or later
- PHP 8.3+
- MySQL 8.0+
- An active Larapen installation with the add-on system enabled
Installation
Step 1: Place the Add-on
Copy or symlink the licenses folder into your Larapen "extensions/addons" directory:
Or, if developing outside the project, create a symlink:
Step 2: Activate the Add-on
Go to Admin → Add-ons → Installed Add-ons and activate License Management.
Alternatively, insert a row in the addons table:
Step 3: Run Migrations
This creates 5 tables: licenses_products, licenses_keys, licenses_activations, licenses_webhook_logs,
and licenses_key_user (pivot). It also adds supported_until to licenses_keys
and license_product_id to shop_products (if the Shop add-on is installed).
Step 4: Set Permissions
The add-on registers 14 permissions (see Permissions). Assign them to admin roles via Admin → Users → Roles & Permissions.
Step 5: Configure
Navigate to Admin → Licenses → Settings and set your API key, key format preferences, and provider credentials. See Configuration.
Configuration
All settings can be configured in two ways:
- Admin panel: Licenses → Settings (stored in the
settingstable, grouplicenses) - Environment variables: in
.env(used as defaults; admin settings override them)
| Setting | Description | Default |
|---|---|---|
api_key |
API key required for all client-facing endpoints. Sent via X-Api-Key header or api_key query parameter. |
(empty) |
default_key_format |
Default format for auto-generated keys: uuid, alphanumeric, prefixed, or hmac_signed. |
alphanumeric |
key_prefix |
Prefix string for the prefixed key format (e.g. LIC). |
LIC |
default_max_activations |
Default activation limit when creating new keys. | 1 |
hmac_key |
Secret key for HMAC-signed key format and webhook signature verification. | (empty, falls back to APP_KEY) |
api_rate_limit |
Maximum API requests per minute per client. | 3 |
api_daily_rate_limit |
Maximum API requests per day per client. | 50 |
Provider-Specific Settings
| Setting | Provider | Description |
|---|---|---|
envato_api_token |
Envato | Personal token from build.envato.com |
envato_author_username |
Envato | Your Envato author username |
webhook_secret |
Webhook | Shared secret for HMAC-SHA256 signature verification |
Environment Variables
Admin: Dashboard
The dashboard (Licenses → Dashboard) provides a real-time overview:
- Stats cards: Total keys, active keys, total products, active activations
- Status breakdown: Suspended, expired, and revoked key counts
- Recent keys: Last 10 created keys with masked display and status badges
- Recent webhooks: Last 5 webhook events with provider and status
Admin: Products
Products represent the software or service being licensed. Each license key belongs to one product.
Creating a Product
Navigate to Licenses → Products → Add Product.
| Field | Required | Description |
|---|---|---|
name |
Yes | Product name (translatable) |
slug |
No | URL-safe identifier. Auto-generated from name if empty. |
description |
No | Product description (translatable, max 2000 chars) |
sku |
No | Stock-keeping unit. Must be unique. |
version |
No | Current product version (e.g. 2.1.0) |
is_active |
— | Toggle. Inactive products are hidden from selection lists. |
features |
No | Key-value pairs defining what this product unlocks. See Feature Flags. |
Admin: License Keys
Creating a Key
Navigate to Licenses → License Keys → Add Key.
| Field | Required | Description |
|---|---|---|
product |
Yes | The product this key belongs to |
key |
No | License key string. Leave empty to auto-generate. |
key_format |
No | Format for auto-generation (see Key Formats) |
license_type |
Yes | Standard, Extended, Trial, or Lifetime |
status |
Yes | Active, Suspended, Expired, or Revoked |
expires_at |
No | Expiration date. Leave empty for non-expiring keys. |
supported_until |
No | Support expiry date. Distinct from license expiry: tracks when the customer’s support period ends (e.g. Envato 6-month support window). |
max_activations |
Yes | How many domains/machines this key can be activated on simultaneously |
provider |
Yes | License source: manual, envato, or webhook |
assigned_user |
No | Optionally link to a user account |
notes |
No | Internal notes (not exposed via API) |
Bulk Operations
Bulk Generate
Navigate to License Keys → Bulk Generate to create up to 1,000 keys at once. Select the product, format, license type, max activations, and optional expiration date.
Bulk Delete
On the keys list page, select multiple keys with checkboxes and click Delete Selected.
CSV Export
Click Export CSV on the keys list page. Supports filtering by product and status before export.
Lifecycle Actions
| Action | Effect |
|---|---|
| Suspend | Sets status to suspended. The key fails verification but can be reactivated later. Existing activations remain but are non-functional. |
| Revoke | Sets status to revoked and deactivates all active activations. This is irreversible in practice. |
| Reactivate | Sets status back to active. The key becomes valid again (if not expired). |
Admin: Activations
View all activations for a specific key by clicking Activations on the key detail page, or navigating to
/admin/licenses/keys/{id}/activations.
Each activation record shows:
- Domain (if provided) — with a Local badge for local/development domains
- Machine ID (if provided)
- IP address
- User agent
- Activation date
- Active/Deactivated status
Admins can manually deactivate any active activation to free up a slot for the customer.
When “Record local domain activations” is enabled in the Local Domain Bypass settings, local activations appear in the list
but do not count toward the key’s max_activations limit. A Delete local activations button
allows bulk-removing all local activation records for a key. The license keys index page provides a filter to show keys with local activations only.
Admin: Feature Flags
Each product can define a set of key-value feature flags on the create/edit form. These are stored as JSON and returned in every /verify API response.
How It Works
- Edit a product and scroll to the Features section.
- Click Add Feature to add key-value rows.
- Set the feature key (e.g.
api_access) and value (e.g.true). - Save. The features are returned as a JSON object in API responses.
Example Feature Sets
Admin: Settings
The settings page (Licenses → Settings) is organized into sections:
- API Configuration: API key and rate limit
- Key Generation: Default format, prefix, max activations, HMAC key
- Local Domain Bypass: Enable/disable bypass, record local activations toggle, and domain patterns
- Envato Provider: Enable/disable, API token, and author username
- Webhook Provider: Webhook secret and URL display
The sidebar displays all available API endpoints and provider configuration status badges.
Admin: Webhook Logs
All incoming webhook events are logged with their provider, event type, raw payload, response data, status (Success / Failed / Ignored), and any error messages. Navigate to Licenses → Webhook Logs to review. Filter by provider or status.
Updating
Step 1: Replace Files
Replace the add-on directory with the new version (or pull the latest if using a symlink to a Git repository).
Step 2: Run Migrations
New migrations are automatically picked up. The add-on uses timestamped migrations that only run once.
Step 3: Clear Caches
Step 4: Verify
Visit Licenses → Dashboard to confirm everything loads correctly. Check the Settings page for any new configuration options introduced in the update.
License Registration Page
A public-facing page where customers register their purchase code (Envato or manual) and activate it on their domain.
Available at /{locale}/licenses/register (or /licenses/register without locale prefix).
URL & Routes
| Method | URL | Route Name | Description |
|---|---|---|---|
| GET | /{locale}/licenses/register |
front.licenses.register.localized |
Show registration form |
| POST | /{locale}/licenses/register |
front.licenses.register.localized |
Process registration |
| GET | /licenses/register |
front.licenses.register |
Non-localized variant |
Registration Flow
- Customer visits
/licenses/registerand fills in their license key / purchase code and domain. - The system searches for the key in the local database.
- If not found locally, it tries the Envato provider: verifies the purchase code via the Envato API.
If valid, a
LicenseKeyrecord is auto-created withprovider = envato. - If the user is authenticated, the
LicenseKeyis linked to their user account (user_id). - The system calls
LicenseActivationService::activate()with the provided domain. - On success, the customer sees a confirmation message. On failure (invalid key, expired, max activations), an error is displayed.
Form Fields
| Field | Validation | Description |
|---|---|---|
license_key |
Required, string, max 255 | The license key or Envato purchase code |
domain |
Required, string, max 255 | The domain where the software is installed (without http/https) |
Theme Views
Each theme provides its own styled version of the registration form at:
Themes: default, creative, elegant, minimalist, olive, technology.
front_menu entry in addon.json. The label “Register License” appears in the header menu.
Customer Portal (My Licenses)
Authenticated customers can view all their linked licenses and manage domain activations.
Requires user login (uses auth middleware).
URL & Routes
| Method | URL | Route Name | Description |
|---|---|---|---|
| GET | /{locale}/licenses/list |
front.licenses.my.localized |
List all user’s licenses |
| GET | /{locale}/licenses/list/{id} |
front.licenses.my.show.localized |
View license details & activations |
| POST | /{locale}/licenses/list/{id}/deactivate |
front.licenses.my.deactivate.localized |
Deactivate a domain |
Non-localized variants (without {locale}) are also available.
My Licenses Page
Displays a list/table of all licenses owned by the logged-in user, showing:
- Product name: linked license product
- License key: masked by default (e.g.,
XXXX-XXXX-XXXX-ab12), with a copy button - Status badge: Active (green), Expired (amber), Suspended (red), Revoked (dark)
- License type: Standard, Extended, Trial, Lifetime
- Activations: count / max (e.g., “2 / 3”)
- Expiry date: or “Never” for lifetime licenses
- View Details link
License Detail Page
Shows complete information for a single license:
- Key Information: full key (masked with reveal toggle via vanilla JS), status, type, issued/expiry dates
- Active Domains table: domain, IP address, activation date, and a Deactivate button for each
- Activation History: all activations (active + deactivated) with timestamps
Deactivation
Customers can self-deactivate domains they no longer use. The deactivation:
- Verifies the license belongs to the authenticated user
- Calls
LicenseActivationService::deactivateByDomain() - Frees up an activation slot for use on another domain
- Redirects back with a success message
Theme Views
user_menu in addon.json, icon: bi-key).
Shop Auto-generation
When the Shop add-on is active and a shop product is linked to a license product, license keys are automatically generated when an order is paid.
Setup
- Run the migration: The migration
2026_03_07_100001_add_license_product_id_to_shop_products_table.phpadds alicense_product_idcolumn toshop_products. - Link shop products to license products: In the shop product admin form,
set the
license_product_idto link it to a license product created under Licenses → Products.
Auto-generation Flow
- A customer completes a purchase via the shop.
- The order’s
payment_statusis updated toPAID(viamarkAsPaid()or payment gateway webhook). - The
ShopOrderObserverdetects the payment status change. - For each order item where the shop product has a
license_product_id:- A
LicenseKeyis created per item quantity (e.g., qty 2 → 2 keys) - Provider is set to
shop - Status is set to
active provider_referenceis set toshop_order:{order_number}(prevents duplicate generation)- The key inherits the default format from settings
- A
- A
LicenseKeyIssuedNotificationemail is sent to the customer with:- Greeting with the customer’s name
- All generated license keys
- A “Register License” button linking to
/licenses/register - Instructions on how to activate
Duplicate Prevention
The observer checks for existing keys with the same provider_reference before generating.
If keys for shop_order:ORD-12345 already exist, the observer skips generation.
This prevents duplicates if the payment status is updated multiple times.
Database Changes
| Table | Column | Type | Description |
|---|---|---|---|
| shop_products | license_product_id | BIGINT UNSIGNED NULL |
FK to licenses_products.id (ON DELETE SET NULL) |
Addons\Shop\Models\Order exists.
updated Eloquent event only (not created).
An order that is created directly with payment_status = PAID will not trigger auto-generation:
the status must transition from a non-paid state to PAID via an update.
Troubleshooting
API returns 503: "License API is not configured"
You have not set an API key. Go to Licenses → Settings and set the API Key field, or set LICENSES_API_KEY in
.env.
API returns 401: "Invalid API key"
The X-Api-Key header or api_key parameter does not match the configured key. Check for trailing spaces or encoding issues.
Activation fails with "Maximum activations reached"
The key has reached its max_activations limit. Options:
- Deactivate an unused domain via
POST /deactivateor from the admin panel - Increase
max_activationson the key in the admin panel
Envato verification fails
Check that:
- The Envato API token has
View Your Envato Account UsernameandVerify Purchasespermissions - The
Author Usernamematches your Envato profile exactly (case-sensitive) - The purchase code is valid and belongs to one of your items
Webhook events are logged as "Failed"
Common causes:
- Invalid signature: The
X-Webhook-Signatureheader does not match. Ensure both sides use the same secret and compute HMAC-SHA256 over the raw request body. - Missing product: The
productslug in the payload does not match any active product. Create the product first. - Invalid event: The
eventfield must be one of:license.created,license.updated,license.revoked,license.expired.
Registration page returns “Invalid license key”
The key was not found locally and Envato verification also failed. Check:
- The Envato API token and author username are correctly configured in Licenses → Settings
- The purchase code is valid and belongs to one of your Envato items
- If using manual keys, ensure the key exists in Licenses → License Keys
Local domain not being bypassed
Check that:
- Local Domain Bypass is enabled in Licenses → Settings (or
LICENSES_LOCAL_DOMAIN_BYPASS=truein.env) - The domain matches one of the configured patterns (patterns use
fnmatch()syntax) - The domain does not include the protocol: use
localhostnothttp://localhost
Shop purchase does not generate license keys
Check that:
- The shop product has a
license_product_idset (linked to a license product) - The order’s
payment_statusis being changed toPAID - The migration
2026_03_07_100001_add_license_product_id_to_shop_products_tablehas been run - Check the
provider_referencecolumn: if keys withshop_order:{order_number}already exist, the observer skips generation
Client-side verification shows “License Invalid”
Check that:
LICENSE_API_BASE_URLpoints to the correct server (e.g.,https://larapen.com/api/licenses)LICENSE_API_KEYmatches theLICENSES_API_KEYon the server- The purchase code is valid and has not been revoked
- The server is reachable from the client (no firewall/DNS issues)