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/verify at boot to check validity and read the features object.
  • No domain activation needed: just verification.

Desktop Application

A desktop app is licensed per machine.

  • On launch, the app calls POST /api/licenses/activate with a machine_id.
  • The app periodically calls GET /api/licenses/verify to check validity.
  • When the user decommissions a machine, call POST /api/licenses/deactivate to 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/activate with domain = 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/verify and reads the features to 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/activate with 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:

  1. Admin panel: Licenses → Settings (stored in the settings table, group licenses)
  2. 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)
Tip: The key string cannot be changed after creation. Choose your format carefully or let the system auto-generate it.

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

  1. Edit a product and scroll to the Features section.
  2. Click Add Feature to add key-value rows.
  3. Set the feature key (e.g. api_access) and value (e.g. true).
  4. 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.

Backup first: Always back up your database before running migrations on a production system.

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

MethodURLRoute NameDescription
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

  1. Customer visits /licenses/register and fills in their license key / purchase code and domain.
  2. The system searches for the key in the local database.
  3. If not found locally, it tries the Envato provider: verifies the purchase code via the Envato API. If valid, a LicenseKey record is auto-created with provider = envato.
  4. If the user is authenticated, the LicenseKey is linked to their user account (user_id).
  5. The system calls LicenseActivationService::activate() with the provided domain.
  6. On success, the customer sees a confirmation message. On failure (invalid key, expired, max activations), an error is displayed.

Form Fields

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

Tip: The registration page is linked from the front-end navigation menu via the 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

MethodURLRoute NameDescription
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:

  1. Verifies the license belongs to the authenticated user
  2. Calls LicenseActivationService::deactivateByDomain()
  3. Frees up an activation slot for use on another domain
  4. Redirects back with a success message

Theme Views

User Menu: The “My Licenses” link appears in the user account dropdown menu (configured via 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

  1. Run the migration: The migration 2026_03_07_100001_add_license_product_id_to_shop_products_table.php adds a license_product_id column to shop_products.
  2. Link shop products to license products: In the shop product admin form, set the license_product_id to link it to a license product created under Licenses → Products.

Auto-generation Flow

  1. A customer completes a purchase via the shop.
  2. The order’s payment_status is updated to PAID (via markAsPaid() or payment gateway webhook).
  3. The ShopOrderObserver detects the payment status change.
  4. For each order item where the shop product has a license_product_id:
    • A LicenseKey is created per item quantity (e.g., qty 2 → 2 keys)
    • Provider is set to shop
    • Status is set to active
    • provider_reference is set to shop_order:{order_number} (prevents duplicate generation)
    • The key inherits the default format from settings
  5. A LicenseKeyIssuedNotification email 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

TableColumnTypeDescription
shop_products license_product_id BIGINT UNSIGNED NULL FK to licenses_products.id (ON DELETE SET NULL)
Requirement: The Shop add-on must be installed and active. The observer is only registered when Addons\Shop\Models\Order exists.
Note: The observer triggers on the 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 /deactivate or from the admin panel
  • Increase max_activations on the key in the admin panel

Envato verification fails

Check that:

  • The Envato API token has View Your Envato Account Username and Verify Purchases permissions
  • The Author Username matches 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-Signature header does not match. Ensure both sides use the same secret and compute HMAC-SHA256 over the raw request body.
  • Missing product: The product slug in the payload does not match any active product. Create the product first.
  • Invalid event: The event field 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=true in .env)
  • The domain matches one of the configured patterns (patterns use fnmatch() syntax)
  • The domain does not include the protocol: use localhost not http://localhost

Shop purchase does not generate license keys

Check that:

  • The shop product has a license_product_id set (linked to a license product)
  • The order’s payment_status is being changed to PAID
  • The migration 2026_03_07_100001_add_license_product_id_to_shop_products_table has been run
  • Check the provider_reference column: if keys with shop_order:{order_number} already exist, the observer skips generation

Client-side verification shows “License Invalid”

Check that:

  • LICENSE_API_BASE_URL points to the correct server (e.g., https://larapen.com/api/licenses)
  • LICENSE_API_KEY matches the LICENSES_API_KEY on the server
  • The purchase code is valid and has not been revoked
  • The server is reachable from the client (no firewall/DNS issues)

Was this article helpful?

Thank you for your feedback!

Still need help? Create a support ticket

Create a Ticket