Documentation Index
Fetch the complete documentation index at: https://docs.authforge.cc/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers patterns for managing licenses tied to recurring subscriptions, whether you use Stripe, Paddle, or any other payment provider.
Lifecycle overview
Creating a subscription license
When a subscription starts, create a license with an expiration matching the billing period:
const response = await fetch("https://api.authforge.cc/v1/licenses", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
appId: process.env.AUTHFORGE_APP_ID,
quantity: 1,
expiresAt: periodEndDate.toISOString(),
maxHwidSlots: 1,
label: `sub_${subscriptionId}`,
}),
});
Store the mapping between your subscription ID and the AuthForge license key in your database.
Renewal
When a subscription renews successfully, extend the license expiration:
await fetch(`https://api.authforge.cc/v1/licenses/${licenseKey}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "extend",
expiresAt: newPeriodEndDate.toISOString(),
}),
});
The user’s SDK session continues uninterrupted; the next heartbeat or re-authentication will pick up the new expiry.
Cancellation
When a user cancels, you have two options:
Revoke the license immediately on cancellation:
await fetch(`https://api.authforge.cc/v1/licenses/${licenseKey}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ action: "revoke" }),
});
Option B: Let it expire
Don’t revoke; let the license expire at the end of the already-paid period. This is the more user-friendly approach. The license expiresAt was set to the billing period end, so it will naturally stop working.
Option B is the standard practice. Users have paid for the current period and should retain access until it ends.
Payment failure
When a payment fails, decide how to handle the grace period:
async function handlePaymentFailed(subscriptionId, attemptCount) {
const record = await db.getLicenseBySubscription(subscriptionId);
if (!record) return;
if (attemptCount >= 3) {
// Final attempt failed; revoke
await revokeLicense(record.licenseKey);
await notifyCustomer(record.email, "subscription_cancelled");
} else {
// Early attempt; warn the user but keep the license active
await notifyCustomer(record.email, "payment_failed");
}
}
Re-subscription
When a user re-subscribes after cancellation or revocation:
- Check if they have an existing (revoked) license key.
- If yes, re-activate it and extend the expiry; this preserves their HWID bindings.
- If no, create a new license.
async function handleResubscription(customerId, newPeriodEnd) {
const existing = await db.getLicenseByCustomer(customerId);
if (existing) {
// Re-activate the existing license
await fetch(`https://api.authforge.cc/v1/licenses/${existing.licenseKey}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ action: "activate" }),
});
// Extend expiry
await fetch(`https://api.authforge.cc/v1/licenses/${existing.licenseKey}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "extend",
expiresAt: newPeriodEnd.toISOString(),
}),
});
} else {
// Create a fresh license
await createNewLicense(customerId, newPeriodEnd);
}
}
Plan upgrades and downgrades
Use license variables to control features per plan tier. When a user upgrades or downgrades:
async function handlePlanChange(licenseKey, newPlan) {
const planConfig = {
basic: { plan: "basic", maxProjects: 3 },
pro: { plan: "pro", maxProjects: 10 },
enterprise: { plan: "enterprise", maxProjects: 50 },
};
await fetch(
`https://api.authforge.cc/v1/licenses/${licenseKey}/variables`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(planConfig[newPlan]),
}
);
}
The user picks up the new plan features the next time they authenticate.
Database schema suggestion
Store the mapping between your payment provider and AuthForge:
CREATE TABLE subscription_licenses (
id SERIAL PRIMARY KEY,
customer_id TEXT NOT NULL,
customer_email TEXT NOT NULL,
subscription_id TEXT UNIQUE NOT NULL,
license_key TEXT UNIQUE NOT NULL,
plan TEXT NOT NULL DEFAULT 'basic',
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Next steps