| name | license-keys |
| description | Guide for implementing license key management with Dodo Payments - activation, validation, and access control for software products. Use when this capability is needed. |
| metadata | {"author":"dodopayments"} |
Dodo Payments License Keys
Reference: docs.dodopayments.com/features/license-keys
License keys authorize access to your digital products. Use them for software licensing, per-seat controls, and gating premium features.
Overview
License keys are unique tokens that:
- Authorize access to software, plugins, CLIs
- Limit activations per user or device
- Gate downloads, updates, or premium features
- Can be linked to subscriptions or one-time purchases
Creating License Keys
In Dashboard
-
Go to Dashboard → License Keys
-
Click "Create License Key"
-
Configure settings:
- Expiry Date: Duration or "no expiry" for perpetual
- Activation Limit: Max concurrent activations (1, 5, unlimited)
- Activation Instructions: Steps for customers
-
Save the license key configuration
Auto-Generation on Purchase
License keys can be automatically generated when a product is purchased:
- Configure your product with license key settings
- When purchased, a key is generated and emailed to customer
license_key.created webhook is fired
API Reference
Public Endpoints (No API Key Required)
These endpoints can be called directly from client applications:
| Endpoint | Description |
|---|
POST /licenses/activate | Activate a license key |
POST /licenses/deactivate | Deactivate an instance |
POST /licenses/validate | Check if key is valid |
Authenticated Endpoints (API Key Required)
| Endpoint | Description |
|---|
GET /license_keys | List all license keys |
GET /license_keys/:id | Get license key details |
PATCH /license_keys/:id | Update license key |
GET /license_key_instances | List activation instances |
Implementation Examples
Activate a License Key
import DodoPayments from 'dodopayments';
const client = new DodoPayments();
async function activateLicense(licenseKey: string, deviceName: string) {
try {
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName,
});
return {
success: true,
instanceId: response.id,
message: 'License activated successfully',
};
} catch (error: any) {
return {
success: false,
message: error.message || 'Activation failed',
};
}
}
Validate a License Key
import DodoPayments from 'dodopayments';
const client = new DodoPayments();
async function validateLicense(licenseKey: string) {
try {
const response = await client.licenses.validate({
license_key: licenseKey,
});
return {
valid: response.valid,
activations: response.activations_count,
maxActivations: response.activations_limit,
expiresAt: response.expires_at,
};
} catch (error) {
return { valid: false };
}
}
Deactivate a License
import DodoPayments from 'dodopayments';
const client = new DodoPayments();
async function deactivateLicense(licenseKey: string, instanceId: string) {
try {
await client.licenses.deactivate({
license_key: licenseKey,
license_key_instance_id: instanceId,
});
return { success: true, message: 'License deactivated' };
} catch (error: any) {
return { success: false, message: error.message };
}
}
Desktop App Integration
Electron App Example
import Store from 'electron-store';
import DodoPayments from 'dodopayments';
const store = new Store();
const client = new DodoPayments();
interface LicenseInfo {
key: string;
instanceId: string;
activatedAt: string;
}
export async function activateLicense(licenseKey: string): Promise<boolean> {
try {
const deviceName = `${os.hostname()} - ${os.platform()}`;
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName,
});
const licenseInfo: LicenseInfo = {
key: licenseKey,
instanceId: response.id,
activatedAt: ().(),
};
store.(, licenseInfo);
;
} (error) {
.(, error);
;
}
}
(): <> {
license = store.() | ;
(!license) {
;
}
{
response = client..({
: license.,
});
response.;
} (error) {
activatedAt = (license.);
daysSinceActivation = (.() - activatedAt.()) / ( * * * );
daysSinceActivation < ;
}
}
(): <> {
license = store.() | ;
(!license) {
;
}
{
client..({
: license.,
: license.,
});
store.();
;
} (error) {
.(, error);
;
}
}
React Component for License Input
import { useState } from 'react';
interface Props {
onActivated: () => void;
}
export function LicenseActivation({ onActivated }: Props) {
const [licenseKey, setLicenseKey] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleActivate = async () => {
setLoading(true);
setError(null);
try {
const success = await window.electronAPI.activateLicense(licenseKey);
if (success) {
onActivated();
} else {
setError('Invalid license key. Please check and try again.');
}
} catch (err) {
setError('Activation failed. Please try again.');
} finally {
();
}
};
(
);
}
CLI Tool Integration
Node.js CLI Example
import Conf from 'conf';
import DodoPayments from 'dodopayments';
import { machineIdSync } from 'node-machine-id';
const config = new Conf({ projectName: 'your-cli' });
const client = new DodoPayments();
export async function activate(licenseKey: string): Promise<void> {
const machineId = machineIdSync();
const deviceName = `CLI - ${process.platform} - ${machineId.substring(0, 8)}`;
try {
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName,
});
config.set('license', {
key: licenseKey,
instanceId: response.id,
machineId,
});
console.log('License activated successfully!');
} (: ) {
(error. === ) {
.();
} (error. === ) {
.();
} {
.(, error.);
}
process.();
}
}
(): <> {
license = config.() ;
(!license) {
;
}
{
response = client..({
: license.,
});
response.;
} {
;
}
}
(): <> {
license = config.() ;
(!license) {
.();
;
}
{
client..({
: license.,
: license.,
});
config.();
.();
} (: ) {
.(, error.);
}
}
() {
() => {
valid = ();
(!valid) {
.();
.();
process.();
}
};
}
CLI Commands
import { Command } from 'commander';
import { activate, deactivate, checkLicense, requireLicense } from './license';
const program = new Command();
program
.command('activate <license-key>')
.description('Activate your license')
.action(activate);
program
.command('deactivate')
.description('Deactivate license on this device')
.action(deactivate);
program
.command('status')
.description('Check license status')
.action(async () => {
const valid = await checkLicense();
console.log(valid ? 'License: Active' : 'License: Not activated');
});
program
.command('generate')
.description('Generate something (requires license)')
.hook('preAction', requireLicense())
.action(async () => {
});
program.parse();
Webhook Integration
Handle License Key Creation
export async function POST(req: NextRequest) {
const event = await req.json();
if (event.type === 'license_key.created') {
const { id, key, product_id, customer_id, expires_at } = event.data;
await prisma.license.create({
data: {
externalId: id,
key: key,
productId: product_id,
customerId: customer_id,
expiresAt: expires_at ? new Date(expires_at) : null,
status: 'active',
},
});
await sendLicenseEmail(customer_id, key, product_id);
}
return NextResponse.json({ received: true });
}
Server-Side Validation
For sensitive operations, validate server-side with your API key:
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function POST(req: NextRequest) {
const { licenseKey } = await req.json();
try {
const licenses = await client.licenseKeys.list({
license_key: licenseKey,
});
if (licenses.items.length === 0) {
return NextResponse.json({ valid: false, error: 'License not found' });
}
const license = licenses.items[0];
const valid =
license.status === &&
(!license. || (license.) > ());
.({
valid,
: license.,
: license.,
: license.,
: license.,
});
} (: ) {
.({ : , : error. }, { : });
}
}
Best Practices
1. Keep Limits Clear
Choose sensible defaults for expiry and activations based on your product type.
2. Guide Users
Provide precise activation instructions:
- "Paste the key in Settings → License"
- "Run:
mycli activate <key>"
- Include self-serve documentation links
3. Validate Server-Side
For critical access control, always validate on your server before granting access.
4. Handle Offline Gracefully
Allow a grace period for offline use in desktop/CLI apps.
5. Monitor Events
Use webhooks to detect abuse patterns and automate revocations.
6. Provide Easy Deactivation
Let users deactivate devices themselves to manage their activation slots.
Common Patterns
Feature Gating
async function canAccessFeature(feature: string, licenseKey: string) {
const { valid } = await validateLicense(licenseKey);
if (!valid) return false;
const featureTiers = {
'basic-export': ['starter', 'pro', 'enterprise'],
'advanced-export': ['pro', 'enterprise'],
'api-access': ['enterprise'],
};
const license = await getLicenseDetails(licenseKey);
return featureTiers[feature]?.includes(license.tier);
}
Subscription-Linked Licenses
When license is linked to a subscription:
if (event.type === 'subscription.cancelled') {
const { customer_id } = event.data;
const licenses = await client.licenseKeys.list({ customer_id });
for (const license of licenses.items) {
await client.licenseKeys.update(license.id, {
status: 'disabled',
});
}
}
Resources
Converted and distributed by TomeVault — claim your Tome and manage your conversions.