# Examples (/examples) import { Flame, AppWindow, WifiOff } from 'lucide-react'; This is a collection of example apps using Keyforge. You can use these examples as a reference for your own projects. } href="https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/electron-app" description="Basic desktop application built with Electron. Uses the Public API, no backend server required." /> } href="https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/offline-electron-app" description="Simple desktop application built with Electron. Licenses are validated offline. No backend server required." /> # Introduction (/) Welcome to the Keyforge docs! This is a collection of guides and resources to help you get started with Keyforge and the API. ## Get started Getting started is easy and only takes a few minutes. Here are some resources to help you get up and running quickly. ### LLMs AI agents can view the Keyforge documentation in Markdown by accessing [`llms-full.txt`](https://docs.keyforge.dev/llms-full.txt). ## Learn more Some resources to help you learn more about Keyforge and its features. ## API Reference Guides on how to use the Keyforge API. # Licenses (/licenses) Each license is associated with a product and can be activated on a defined number of devices. A license can also have an email associated with it, allowing customers to manage their licenses in the [portal](/portal). ## License types Keyforge currently supports two types of licenses: * **Perpetual**: A license that never expires. * **Timed**: A license that expires at a specific date. ## Devices A license can be activated on multiple devices, as defined by the maximum active devices. Each active device on a license has 3 properties: * **Identifier**: A unique identifier for the device. Must have at most 96 characters. * **Name**: A name for the device. Does not need to be unique. Must have at most 64 characters. * **Activation date**: The date when the device was activated. The device identifier needs to be unique inside the license scope. There are various ways to get a unique identifier, such as the MAC address, HWID, serial number, or a randomly generated UUID stored in the device. # Portal (/portal) Customers can view and manage their licenses in the Keyforge portal. To access it, customers can request an email with a link to the portal. All licenses linked to their email address will be available. If you prefer not to make your products available for customers to manage in the portal, you can hide them in the [dashboard](https://keyforge.dev/dashboard/portal). You can also prevent customers from resetting active devices. ## Accessing the portal To access the portal, customers must request an access link via email. This link is valid for 2 hours. Customers can request the portal access link here: [https://keyforge.dev/portal/request](https://keyforge.dev/portal/request). You can pre-fill the email field in the portal request form by adding the `email` query parameter to the URL. For example: [https://keyforge.dev/portal/request?email=john-doe@example.com](https://keyforge.dev/portal/request?email=john-doe@example.com). ### Private sessions You can also create a private portal session for a specific email address via the [dashboard](https://keyforge.dev/dashboard/portal) or [API](/api-reference/portal/create-private-session). Customers will only be able to view and manage licenses from your products. The generated link is valid for 12 hours. # Products (/products) A product is a software or service that you want to license and distribute to your customers. ## Customization ### Support email A customer support email displayed in purchase emails and the portal. It's optional, but if not provided, customers will be instructed to contact you through your Keyforge account email. ### Purchase note A note displayed in purchase emails and the portal, usually to guide customers on how to activate their license. ### Action button A button displayed in purchase emails and the portal, usually used as a download button. You can define its text and link. You can add the license key to the action button link by using the `{key}` placeholder. For example, `https://example.com/download?key={key}`. ## Purchase email example Here is an example of a customized purchase email sent to customers: If you're in the **Plus** tier, you can add an image to your product. This image is currently only displayed in purchase emails. # Quickstart (/quickstart-server) You can set up Keyforge in your project within minutes. This guide will walk you through the steps to get started. Choose whether to integrate Keyforge directly in your client or server. *** ## Managing licenses in the server To manage licenses in your server, use the Keyforge API. You need an API key to use it. ### Prerequisites Before you begin, make sure you have completed the following steps: * Get an API key [here](https://keyforge.dev/dashboard/api-keys). * Create a product [here](https://keyforge.dev/dashboard/products). ### Create a license In this example, we will create a license that never expires and can only have 1 device active at the same time. Copy the returned license key. cURL JavaScript ```bash curl -X POST https://keyforge.dev/api/v1/licenses \ -H "Authorization: Bearer sk_1234" \ -H "Content-Type: application/json" \ -d '{ "productId": "p_123456", "type": "perpetual", "maxDevices": 1, "email": "john-doe@example.com" }' ``` ```js const res = await fetch('https://keyforge.dev/api/v1/licenses', { method: 'POST', headers: { Authorization: 'Bearer sk_1234', 'Content-Type': 'application/json', }, body: JSON.stringify({ productId: 'p_123456', type: 'perpetual', maxDevices: 1, email: 'john-doe@example.com', }), }); const license = await res.json(); console.log(license.key); ``` Example response from the API when creating a license: ```json { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a", "productId": "p_123456", "type": "perpetual", "expiresAt": null, "revoked": false, "email": "john-doe@example.com", "maxDevices": 1, "activeDevices": [], "createdAt": "2024-05-19T18:39:33.000Z" } ``` ### Activate a license Activate the license you have just created. Replace `LICENSE_KEY` with the license key you copied in the previous step. In this example, we will activate the license on a device with the identifier `some_device_id` and the name `My computer name`. cURL JavaScript ```bash curl -X POST "https://keyforge.dev/api/v1/licenses/activate" \ -H "Authorization: Bearer sk_1234" \ -H "Content-Type: application/json" \ -H "License-Key: LICENSE_KEY" \ -d '{ "productId": "p_123456", "device": { "identifier": "some_device_id", "name": "My computer name" } }' ``` ```js await fetch('https://keyforge.dev/api/v1/licenses/activate', { method: 'POST', headers: { Authorization: 'Bearer sk_1234', 'Content-Type': 'application/json', 'License-Key': 'LICENSE_KEY', }, body: JSON.stringify({ productId: 'p_123456', device: { identifier: 'some_device_id', name: 'My computer name', }, }), }); ``` Example response from the API when activating a license: ```json { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a", "productId": "p_123456", "type": "perpetual", "expiresAt": null, "revoked": false, "email": "john-doe@example.com", "maxDevices": 1, "activeDevices": [ { "identifier": "some_device_id", "name": "My computer name", "activatedAt": "2024-05-19T18:39:33.000Z" } ], "createdAt": "2024-05-19T18:39:33.000Z" } ``` ### Validate a license Validate the license you have just activated. Replace `LICENSE_KEY` with the license key. In this example, we will verify the license on the device with the identifier `some_device_id`. cURL JavaScript ```bash curl -X POST "https://keyforge.dev/api/v1/licenses/validate" \ -H "Authorization: Bearer sk_1234" \ -H "Content-Type: application/json" \ -H "License-Key: LICENSE_KEY" \ -d '{ "productId": "p_123456", "deviceIdentifier": "some_device_id" }' ``` ```js const res = await fetch('https://keyforge.dev/api/v1/licenses/validate', { method: 'POST', headers: { Authorization: 'Bearer sk_1234', 'Content-Type': 'application/json', 'License-Key': 'LICENSE_KEY', }, body: JSON.stringify({ productId: 'p_123456', deviceIdentifier: 'some_device_id', }), }); const validation = await res.json(); console.log(validation.isValid); ``` Example response from the API when validating a license: ```json { "isValid": true, "status": "active", "device": { "identifier": "some_device_id", "name": "My computer name", "activationDate": "2024-05-19T18:39:33.000Z" }, "license": { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a", "productId": "p_123456", "type": "perpetual", "expiresAt": null, "revoked": false, "email": "john-doe@example.com", "maxDevices": 1, "activeDevices": [ { "identifier": "some_device_id", "name": "My computer name", "activationDate": "2024-05-19T18:39:33.000Z" } ], "createdAt": "2024-05-19T18:39:33.000Z" } } ``` ### Congratulations! 🎉 You have successfully created, activated, and validated your first license. You can manage all licenses in the [dashboard](https://keyforge.dev/dashboard/licenses). ## Learn more ### Next steps Some features to explore and extend your licensing system. ### Public API Keyforge also provides a public API that you can use directly in your app, without the need to create a backend. ### API Reference Learn more about the Keyforge API. # Quickstart (/quickstart) You can set up Keyforge in your project within minutes. This guide will walk you through the steps to get started. Choose whether to integrate Keyforge directly in your client or server. *** ## Licensing a client application To validate and activate licenses in the client, use the [Public API](/api-reference/public/public-validate-license) directly in your app. You can use this API anywhere, with any programming language. ### Prerequisites Before you begin, make sure you have completed the following steps: * Create a product [here](https://keyforge.dev/dashboard/products). * Create a license [here](https://keyforge.dev/dashboard/licenses). ### Activate a license The first time a user opens your app, they should be prompted to activate their license. The device identifier needs to be unique inside the license scope. You can use a MAC address, HWID, or any other type of identifier that is unique to a device. cURL JavaScript Python Go Swift Rust ```bash curl -X POST https://keyforge.dev/api/v1/public/licenses/activate \ -H "Content-Type: application/json" \ -d '{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "deviceName": "My device", "productId": "p_123456" }' ``` ```js await fetch('https://keyforge.dev/api/v1/public/licenses/activate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', deviceIdentifier: 'some_device_id', deviceName: 'My device', productId: 'p_123456', }), }); console.log('License activated'); ``` ```py import requests requests.post( 'https://keyforge.dev/api/v1/public/licenses/activate', json={ 'licenseKey': 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', 'deviceIdentifier': 'some_device_id', 'deviceName': 'My device', 'productId': 'p_123456', }, headers={ 'Content-Type': 'application/json', }, ) print('License activated') ``` ```go package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" ) func main() { data := map[string]string{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "deviceName": "My device", "productId": "p_123456", } jsonData, _ := json.Marshal(data) req, _ := http.NewRequest("POST", "https://keyforge.dev/api/v1/public/licenses/activate", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("Request failed:", err) return } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) if resp.StatusCode == http.StatusOK { fmt.Println("License activated successfully:", string(body)) } else { fmt.Println("Error activating license:", resp.Status, string(body)) } } ``` ```swift import Foundation Task { let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/activate")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "deviceName": "My device", "productId": "p_123456" ] request.httpBody = try! JSONSerialization.data(withJSONObject: body) _ = try! await URLSession.shared.data(for: request) print("License activated") } ``` ```rust use reqwest::Client; use serde_json::json; #[tokio::main] async fn main() { let client = Client::new(); client .post("https://keyforge.dev/api/v1/public/licenses/activate") .json(&json!({ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "deviceName": "My device", "productId": "p_123456" })) .send() .await .unwrap(); println!("License activated"); } ``` The `productId` property can also be an array of IDs if your app supports multiple products, like different tiers. Example response from the API if the activation is successful. ```json { "isValid": true, "device": { "identifier": "some_device_id", "name": "My computer name", "activationDate": "2023-05-19T18:39:33.000Z" }, "license": { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "productId": "p_123456", "type": "perpetual", "revoked": false, "maxDevices": 5, "expiresAt": null, "createdAt": "2023-05-19T18:39:33.000Z" } } ``` If the activation is successful, `isValid` is always `true`. The `device` and `license` properties will never be `null` in this case. ### Validate a license When your app starts, you can verify the license to check if it's valid. You can validate a license whenever you like, for example, every hour, to ensure that it is still valid. cURL JavaScript Python Go Swift Rust ```bash curl -X POST https://keyforge.dev/api/v1/public/licenses/validate \ -H "Content-Type: application/json" \ -d '{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "productId": "p_123456" }' ``` ```js const res = await fetch( 'https://keyforge.dev/api/v1/public/licenses/validate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', deviceIdentifier: 'some_device_id', productId: 'p_123456', }), } ); const data = await res.json(); console.log(data.isValid); ``` ```py import requests res = requests.post( 'https://keyforge.dev/api/v1/public/licenses/validate', json={ 'licenseKey': 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', 'deviceIdentifier': 'some_device_id', 'productId': 'p_123456', }, headers={ 'Content-Type': 'application/json', }, ) data = res.json() print(data['isValid']) ``` ```go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { data := map[string]string{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "productId": "p_123456", } jsonData, _ := json.Marshal(data) req, _ := http.NewRequest("POST", "https://keyforge.dev/api/v1/public/licenses/validate", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("Request failed:", err) return } defer resp.Body.Close() var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result["isValid"]) } ``` ```swift import Foundation Task { let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/validate")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "productId": "p_123456" ] request.httpBody = try! JSONSerialization.data(withJSONObject: body) let (data, _) = try! await URLSession.shared.data(for: request) let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any] print(json["isValid"] ?? "Missing isValid") } ``` ```rust use reqwest::Client; use serde_json::json; #[tokio::main] async fn main() { let client = Client::new(); let res = client .post("https://keyforge.dev/api/v1/public/licenses/validate") .json(&json!({ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "productId": "p_123456" })) .send() .await .unwrap() .json::() .await .unwrap(); println!("{}", res["isValid"]); } ``` The `productId` property can also be an array of IDs if your app supports multiple products, like different tiers. Example response from the API if the license is valid. ```json { "isValid": true, "device": { "identifier": "some_device_id", "name": "My computer name", "activationDate": "2023-05-19T18:39:33.000Z" }, "license": { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "productId": "p_123456", "type": "perpetual", "revoked": false, "maxDevices": 5, "expiresAt": null, "createdAt": "2023-05-19T18:39:33.000Z" } } ``` The `device` and `license` properties will be `null` if the license is not valid. ### Congratulations! 🎉 You have successfully activated and validated your first license inside your app. You can manage all licenses in the [dashboard](https://keyforge.dev/dashboard/licenses). ## Learn more ### Next steps Some features to explore and extend your licensing system. ### Public API Learn more about the Public API. An API key is not needed to use it. # Offline licensing (/addons/license-token-js-sdk) It's common to have apps that need to work even without an internet connection. Keyforge makes it easy to validate licenses oflline. To make this possible, Keyforge can issue a signed **license token** that can be verified on the client. The token is a JWT and contains information about the license. An internet connection is only required to activate the license and to occasionally refresh the license token. ## Getting started There is a client SDK available for JavaScript, but license tokens can be used in any programming language that supports JWTs. ### Configure license tokens for a product Go to [license tokens](https://keyforge.dev/dashboard/addons/license-token) and add a new product. You can edit how much time a token will be valid for, and other options after creating the new configuration. For setups with more than one product, you can duplicate the signing key pair from another product inside the **edit** menu. You can also import an external key pair. ### Install the client SDK Install the Keyforge client SDK in your project: npm pnpm yarn bun ```bash npm i @keyforge/client ``` ```bash pnpm add @keyforge/client ``` ```bash yarn add @keyforge/client ``` ```bash bun add @keyforge/client ``` ### Retrieve initial token The simplest way to get and store the first license token for a device is after activating a license. The SDK provides `activateLicense`. ```js import { activateLicense } from '@keyforge/client'; const { isValid, token } = await activateLicense({ licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', deviceIdentifier: 'some_device_id', deviceName: 'My device', productId: 'p_123456', }); if (isValid && token) { storeToken(token); // Store the token on the device console.log('License activated successfully!'); } ``` ### Validate and refresh tokens The SDK provides a simple way to validate and automatically refresh license tokens. You should call `validateAndRefreshToken` when your app starts. ```js import { validateAndRefreshToken } from '@keyforge/client/token'; const PUBLIC_KEY = '...'; // Copied from the dashboard. In JSON string or object format const { isValid, token, data, isValidButExpired } = await validateAndRefreshToken({ token: getStoredToken(), // The current license token publicKeyJwk: PUBLIC_KEY, deviceIdentifier: 'some_device_id', productId: 'p_123456', }); if (isValid) { storeToken(token); // Store the new token if it was refreshed console.log('License token is valid!', data.license.key); } else if (!isValidButExpired) { // A network error probably occurred. The token is expired, but was valid // You should NOT prompt the user to activate a license } // If the token is not valid, you should prompt the user to activate a license ``` For periodic token checks, it's not recommended to use `validateAndRefreshToken`. Instead, use `verifyToken`, which only checks the validity of the token without refreshing it. ```js import { verifyToken } from '@keyforge/client/token'; const PUBLIC_KEY = '...'; const { isValid, data } = await verifyToken({ token: getStoredToken(), publicKeyJwk: PUBLIC_KEY, deviceIdentifier: 'some_device_id', productId: 'p_123456', }); if (isValid) { console.log('License token is valid!', data.license.key); } ``` To retrieve a new token from the Keyforge API, you can use `fetchToken`. ```js import { fetchToken } from '@keyforge/client/token'; const { isValid, token } = await fetchToken({ licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE', deviceIdentifier: 'some_device_id', productId: 'p_123456', }); if (isValid) { storeToken(token); } ``` The `fetchToken` function does not verify if the new token is valid. You should always verify the new token. ## Learn more ### Example An example desktop app built with Electron using the client SDK and license tokens is available [here](https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/offline-electron-app). ### API Reference # Offline licensing (/addons/license-token-no-sdk) It's common to have apps that need to work even without an internet connection. Keyforge makes it easy to validate licenses oflline. To make this possible, Keyforge can issue a signed **license token** that can be verified on the client. The token is a JWT and contains information about the license. An internet connection is only required to activate the license and to occasionally refresh the license token. ## Getting started There is a client SDK available for JavaScript, but license tokens can be used in any programming language that supports JWTs. ### Configure license tokens for a product Go to [license tokens](https://keyforge.dev/dashboard/addons/license-token) and add a new product. You can edit how much time a token will be valid for, and other options after creating the new configuration. For setups with more than one product, you can duplicate the signing key pair from another product inside the **edit** menu. You can also import an external key pair. ### Retrieve initial token The simplest way to get and store the first license token for a device is after activating a license. You should use the [activate license](/api-reference/public/activate-license) API endpoint. ```bash curl -X POST https://keyforge.dev/api/v1/public/licenses/activate \ -H "Content-Type: application/json" \ -d '{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "deviceName": "My device", "productId": "p_123456" }' ``` A `token` property will be returned in the response. Store this token in the device's storage. Example response from the API if the activation is successful. ```json { "isValid": true, "token": "...", // The license token "device": { "identifier": "some_device_id", "name": "My device", "activationDate": "2023-05-19T18:39:33.000Z" }, "license": { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "productId": "p_123456", "type": "perpetual", "revoked": false, "maxDevices": 5, "expiresAt": null, "createdAt": "2023-05-19T18:39:33.000Z" } } ``` ### Verify the token To verify the token, you can use any [JWT library](https://jwt.io/libraries) available in your programming language. Here are some tips you should follow: * The token is signed using an **ES256** key pair. The public key is in the [dashboard](https://keyforge.dev/dashboard/addons/license-token). * Check the `exp` claim to see if the token is still valid. * Check the `productId` and `deviceIdentifier` to make sure the token is valid for the current product and device. * Do not ask the user to activate a license if the token is expired but was valid at some point. The token should be verified when the app starts, but it can also be verified periodically. ### Refresh the token The token needs to be refreshed periodically to ensure it remains valid. Use the [license token](/api-reference/public/license-token) API endpoint. You should refresh the token some time before it expires, for example, 3 days before the expiration date. ```bash curl -X POST https://keyforge.dev/api/v1/public/licenses/token \ -H "Content-Type: application/json" \ -d '{ "licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "deviceIdentifier": "some_device_id", "productId": "p_123456" }' ``` A `token` property will be returned in the response. Store this token in the device's storage. Example response from the API if the token was signed successfully. ```json { "isValid": true, "token": "..." // The license token } ``` ## Learn more ### Token payload A license token contains the following data: ```json { "license": { "key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE", "productId": "p_123456", "type": "perpetual", "expiresAt": null, "createdAt": 1684521573, // Unix timestamp in seconds "maxDevices": 5, "email": null }, "device": { "identifier": "some_device_id", "name": "My device", "activationDate": 1684521573 // Unix timestamp in seconds } } ``` There are also some additional claims in the token, such as `exp` (expiration time) and `iat` (issued at time). It is signed using the **ES256** algorithm. ### API Reference # Payments (/addons/payments-one-time) Keyforge uses Stripe restricted API keys with only the necessary permissions. The secret keys are securely stored and encrypted with AES-256. Keyforge integrates with Stripe for automatic license generation. No code or webhook setup needed. *** ## Setup one-time purchases Upon purchasing a Stripe product, a license is automatically created on Keyforge, and the customer receives a thank you email with the license key. Make sure you have a Stripe account, a product set up on Stripe, and a corresponding product on Keyforge. ### Connect Stripe account Go to [payments](https://keyforge.dev/dashboard/integrations/stripe) and click on "Connect account". A Stripe webhook pointing to Keyforge will be automatically created. ### Connect Stripe product Click on "Connect product". Your Stripe accounts and products with their prices will be automatically listed. Select the Stripe product price and the corresponding Keyforge product that customers will receive a license for upon purchase. Choose the options for the created licenses. ### You're done! 🎉 It's as simple as that! Create a payment link for your product and start selling. When a customer purchases the product, a license will be automatically created on Keyforge and they will get emailed the license key. ## Learn more ### Timed licenses You can choose between two options for creating timed licenses using the Stripe integration: 1. **Offset**: The expiration date of the license will be set to the creation date plus the defined offset. 2. **Fixed**: All created licenses will share the same expiration date, as specified in the options. If a customer purchases a fixed timed license that has already expired, they will get an inactive license that **will not work**. ### Invoices If you enable invoice generation in the Stripe payment link, customers will be able to download their invoice from the [portal](/portal). You can also download the invoice from the dashboard. # Payments (/addons/payments-subscription) Keyforge uses Stripe restricted API keys with only the necessary permissions. The secret keys are securely stored and encrypted with AES-256. Keyforge integrates with Stripe for automatic license generation. No code or webhook setup needed. *** ## Setup subscriptions Upon subscribing to a Stripe product, a license is automatically created on Keyforge, and the customer receives a thank you email with the license key. Make sure you have a Stripe account, a recurring product set up on Stripe, and a corresponding product on Keyforge. ### Connect Stripe account Go to [payments](https://keyforge.dev/dashboard/integrations/stripe) and click on "Connect account". A Stripe webhook pointing to Keyforge will be automatically created. ### Connect Stripe subscription Click on "Connect product". Your Stripe accounts and products with their prices will be automatically listed. Select the Stripe recurring price and the corresponding Keyforge product that customers will receive a license for upon subscribing. Don't forget to enable the [billing portal](https://dashboard.stripe.com/settings/billing/portal) on your Stripe account, customers will use it to manage their subscriptions. ### You're done! 🎉 It's as simple as that! Create a payment link for your product and start selling. When a customer subscribes to the product, a license will be automatically created on Keyforge and they will get emailed the license key. ## Learn more ### License expiration & renewals Licenses are **timed** and expire if the subscription isn't renewed. The expiration date updates automatically when an invoice is paid. ### Subscription management #### Changing plans If customers switch plans in the Stripe billing portal, ensure each plan has a connected corresponding Keyforge product. The license will be updated accordingly. #### Plan quantity For plans with a quantity greater than 1, the license **maximum active devices** will be multiplied by the quantity. #### Disconnecting & unlinking * Removing a connected Stripe subscription won't stop license updates on renewal. * To **unlink a subscription** from a license, do so on the licenses page, but note that the license will no longer update on renewal, and the customer may continue paying. ### Free Trials You can offer free trials by setting them up in Stripe. No change is needed on Keyforge. # Activate license (/api-reference/licenses/activate-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Activate a license for a device. # Create license (/api-reference/licenses/create-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Create a new license. # Delete license (/api-reference/licenses/delete-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Delete an existing license. # Get license (/api-reference/licenses/get-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieve information about a license. # Remove device (/api-reference/licenses/remove-device) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Remove a specific active device from a license. # Reset devices (/api-reference/licenses/reset-devices) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Reset all active devices for a license. # Update license (/api-reference/licenses/update-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Update an existing license. # Validate license (/api-reference/licenses/validate-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Verify if a license is valid. # Create private session (/api-reference/portal/create-private-session) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Create a private portal session showing only your Keyforge products. # Create product (/api-reference/products/create-product) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Create a new product. # Delete product (/api-reference/products/delete-product) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Delete an existing product. # Get product (/api-reference/products/get-product) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieve information about a product. # List products (/api-reference/products/list-products) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieve a list of products. # Update product (/api-reference/products/update-product) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Update an existing product. # Activate license (/api-reference/public/public-activate-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Activate a license for a device. # Get license token (/api-reference/public/public-license-token) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get a new signed token for a license. License tokens need to be configured for the product to use this endpoint. # Get license token public key (/api-reference/public/public-product-license-token-public-key) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get the public key JWK of a product for verifying license tokens. This endpoint is only available if the product supports license tokens and exposes its public key. # Validate license (/api-reference/public/public-validate-license) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Verify if a license is valid.