---
title: "How to Add License Verification to Your Software"
description: "Step-by-step guide for developers to integrate 3DIMLI's license verification API into their software product. Verify buyer purchases, enforce seat limits, and manage activations."
canonical_url: "https://support.3dimli.com/software-license-integration"
md_url: "https://support.3dimli.com/software-license-integration.md"
source: "docs/Guides/software-license-integration.md"
x_aeo_version: "1.0"
estimated_tokens: 5100
---

# How to Add License Verification to Your Software

A step-by-step guide for developers listing software on 3DIMLI to verify buyer purchases and enforce license terms using the 3DIMLI License Verification API.

When you sell software on 3DIMLI, each buyer receives a unique **Order Item ID**. This acts as their license key. You can verify this key against 3DIMLI's API from your own software or license server to confirm the purchase is legitimate and determine which license tier the buyer purchased.

This guide walks you through the entire process, from listing your software to building a working license server.

---

## Overview

```
┌──────────────┐ ┌──────────────────────┐ ┌─────────────┐
│ Buyer's App │───▶│ Your License Server │───▶│ 3DIMLI API │
│ │ │ (holds Bearer token) │ │ │
│ Enters their │ │ • Verifies key │ │ Returns: │
│ license key │ │ • Tracks devices │ │ • valid │
│ │◀───│ • Enforces seats │◀───│ • product │
└──────────────┘ └──────────────────────┘ │ name │
 │ • license │
 │ name │
 └─────────────┘
```

---

## Step 1: Create Your Software Product on 3DIMLI

1. Go to [New Product](https://www.3dimli.com/dashboard/seller/products/new) and select **Software**.
2. Fill in the product details (title, description, category, and tags).
3. Set up your **software license tiers**. For example:

| License Tier | Description | Seats | Price |
|---|---|---|---|
| Standard License | Personal and commercial use | 1 device | $29 |
| Team License | Up to 5 users | 5 devices | $99 |
| Company License | Unlimited within one organization | Unlimited | $299 |

4. Upload your software as a compressed archive (ZIP, RAR, 7Z, etc.).
5. Include a **README** in the archive with installation instructions and where to enter the license key.
6. Submit for review.

For the full product creation guide, see [Software Product Type](/creating-products/software).

---

## Step 2: Get Your Product Slug

Your **product slug** is the URL path of your product on the 3DIMLI store. You'll embed this in your software to scope license verification to your product.

Find it in your product's store URL:

```
https://www.3dimli.com/<your-product-slug>
 └── product_slug ──┘
```

For example, if your product page is at `https://www.3dimli.com/software/my-tool`, your product slug is `/my-tool` (the API also accepts `my-tool` or `software/my-tool`).

You can also copy it from the browser address bar when viewing your product page in the store.

:::note
The product slug is **not a secret** — it's visible in the store URL. Knowing it gives no attack surface since it's useless without a valid purchased license key.
:::

---

## Step 3: Understand the Verification API

3DIMLI provides a single verification endpoint, authenticated with a seller Bearer token:

```http
POST https://www.3dimli.com/api/software/v1/verify
Authorization: Bearer <your_seller_token>
Content-Type: application/json
```

```json
{
 "key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "product_slug": "/my-tool"
}
```

| Field | Who provides it | Description |
|---|---|---|
| `Authorization` header | Seller (held on your license server) | Your seller license-verification token from [Dashboard → Settings → API Tokens](https://www.3dimli.com/dashboard/seller/settings/api-tokens) |
| `key` | Buyer (typed into software UI) | The license key from their purchase confirmation email |
| `product_slug` | Seller (hardcoded in your license server config) | The product's URL slug from the store (e.g., `/my-tool`) |

**Response (valid):**

```json
{
 "valid": true,
 "productName": "My Tool",
 "licenseName": "Professional",
 "variant": { "title": "Windows" }
}
```

**Response (invalid):**

```json
{
 "valid": false
}
```

**Response (rate limited):**

```
HTTP 429 Retry-After: 45
{ "valid": false }
```

- A **seller Bearer token** is required. The endpoint is rate-limited at 30 requests/minute per IP after token authentication.
- The token is shown once when generated. Store it server-side — never embed it in your shipped software binary.
- The `productName` is the title of your product on 3DIMLI.
- The `licenseName` matches whatever license tier name you set when creating the product.
- The `variant` field is included only when the purchased product has variants enabled.
- Refunded or incomplete orders return ``.
- The API never leaks information about why a key is invalid — it always returns the same `` response.
- A `401` means the token is missing/invalid; a `403` means the token is valid but doesn't own the requested product slug or your platform subscription has lapsed.

For the full API reference, see [Software License Verification API](/creating-products/software/license-verification-api).

---

## Step 4: Generate a Seller License-Verification Token

Verification calls require a Bearer token bound to your seller account.

1. Sign in to your seller account on [3DIMLI](https://www.3dimli.com).
2. Open [Dashboard → Settings → API Tokens](https://www.3dimli.com/dashboard/seller/settings/api-tokens).
3. Generate a **license-verification** token.
4. Copy the token from the one-time dialog and store it as a secret on the server you'll deploy in Step 5 (e.g., as the `DIMLI_SELLER_TOKEN` environment variable).

:::warning Don't ship the token inside your software
Anyone who downloads your software can extract embedded strings from the binary. Always proxy verification through a license server you control and keep the token there.
:::

The same token works for every product you own. There's no need to generate a separate one per product.

---

## Step 5: Build a License Server

3DIMLI confirms a purchase happened — it does **not** enforce how many machines use a key, and verification calls require your seller token, which must stay server-side. Your license server sits between your software and 3DIMLI: it holds the token, calls the verify endpoint, caches results, and (optionally) enforces per-seat limits.

### Node.js Example (Express)

```javascript
const express = require("express");
const app = express();
app.use(express.json());

// ─── Configuration ───────────────────────────────────────
const PRODUCT_SLUG = "/my-tool"; // From Step 2
const SELLER_TOKEN = process.env.DIMLI_SELLER_TOKEN; // From Step 4 — never hardcode

// Map your license tier names to seat limits
const SEAT_LIMITS = {
 "Standard License": 1,
 "Team License": 5,
 "Company License": -1, // -1 = unlimited
};

// ─── Database (use a real database in production) ────────
const activations = new Map(); // licenseKey -> { licenseName, devices: Set }

// ─── Activate endpoint ──────────────────────────────────
app.post("/activate", async (req, res) => {
 const { licenseKey, deviceId } = req.body;

 if (!licenseKey || !deviceId) {
 return res.status(400).json({ error: "licenseKey and deviceId required" });
 }

 // Check cache first (avoid unnecessary API calls)
 let cached = activations.get(licenseKey);

 if (!cached) {
 // Verify with 3DIMLI
 const response = await fetch(
 "https://www.3dimli.com/api/software/v1/verify",
 {
 method: "POST",
 headers: {
 "Authorization": `Bearer ${SELLER_TOKEN}`,
 "Content-Type": "application/json",
 },
 body: JSON.stringify({ key: licenseKey, product_slug: PRODUCT_SLUG }),
 }
 );
 const result = await response.json();

 if (response.status === 401 || response.status === 403) {
 console.error("3DIMLI auth failed - check token", result);
 return res.status(503).json({ error: "License service unavailable" });
 }

 if (!result.valid) {
 return res.status(403).json({ error: "Invalid license key" });
 }

 cached = { licenseName: result.licenseName, devices: new Set() };
 activations.set(licenseKey, cached);
 }

 // Check seat limit
 const seatLimit = SEAT_LIMITS[cached.licenseName] ?? 1;

 if (cached.devices.has(deviceId)) {
 return res.json({
 activated: true,
 licenseName: cached.licenseName,
 });
 }

 if (seatLimit !== -1 && cached.devices.size >= seatLimit) {
 return res.status(403).json({
 error: `Seat limit reached (${seatLimit}). Deactivate a device first.`,
 currentDevices: cached.devices.size,
 maxDevices: seatLimit,
 });
 }

 // Register device
 cached.devices.add(deviceId);

 res.json({ activated: true, licenseName: cached.licenseName });
});

// ─── Deactivate endpoint ─────────────────────────────────
app.post("/deactivate", (req, res) => {
 const { licenseKey, deviceId } = req.body;
 const cached = activations.get(licenseKey);

 if (cached) {
 cached.devices.delete(deviceId);
 }

 res.json({ deactivated: true });
});

// ─── Check status endpoint ──────────────────────────────
app.post("/status", (req, res) => {
 const { licenseKey } = req.body;
 const cached = activations.get(licenseKey);

 if (!cached) {
 return res.json({ activated: false });
 }

 const seatLimit = SEAT_LIMITS[cached.licenseName] ?? 1;

 res.json({
 activated: true,
 licenseName: cached.licenseName,
 devicesUsed: cached.devices.size,
 maxDevices: seatLimit === -1 ? "unlimited" : seatLimit,
 });
});

app.listen(3000, () => console.log("License server running on port 3000"));
```

### Python Example (Flask)

```python
import os
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

PRODUCT_SLUG = "/my-tool" # From Step 2
SELLER_TOKEN = os.environ["DIMLI_SELLER_TOKEN"] # From Step 4 — never hardcode

SEAT_LIMITS = {
 "Standard License": 1,
 "Team License": 5,
 "Company License": -1, # unlimited
}

activations = {} # license_key -> { "licenseName": str, "devices": set }

@app.route("/activate", methods=["POST"])
def activate():
 data = request.json
 license_key = data.get("licenseKey")
 device_id = data.get("deviceId")

 if not license_key or not device_id:
 return jsonify({"error": "licenseKey and deviceId required"}), 400

 cached = activations.get(license_key)

 if not cached:
 # Verify with 3DIMLI
 resp = requests.post(
 "https://www.3dimli.com/api/software/v1/verify",
 headers={"Authorization": f"Bearer {SELLER_TOKEN}"},
 json={"key": license_key, "product_slug": PRODUCT_SLUG},
 )
 result = resp.json()

 if resp.status_code in (401, 403):
 return jsonify({"error": "License service unavailable"}), 503

 if not result.get("valid"):
 return jsonify({"error": "Invalid license key"}), 403

 cached = {"licenseName": result.get("licenseName"), "devices": set()}
 activations[license_key] = cached

 seat_limit = SEAT_LIMITS.get(cached["licenseName"], 1)

 if device_id in cached["devices"]:
 return jsonify({"activated": True, "licenseName": cached["licenseName"]})

 if seat_limit != -1 and len(cached["devices"]) >= seat_limit:
 return jsonify({"error": f"Seat limit reached ({seat_limit})"}), 403

 cached["devices"].add(device_id)
 return jsonify({"activated": True, "licenseName": cached["licenseName"]})

@app.route("/deactivate", methods=["POST"])
def deactivate():
 data = request.json
 cached = activations.get(data.get("licenseKey"))
 if cached:
 cached["devices"].discard(data.get("deviceId"))
 return jsonify({"deactivated": True})

if __name__ == "__main__":
 app.run(port=3000)
```

---

## Step 6: Add License Check to Your Software

Your software calls **your license server** (not 3DIMLI directly), since the seller token must stay server-side.

### Desktop App Example (JavaScript/Electron)

```javascript
const os = require("os");
const crypto = require("crypto");

// Generate a stable device ID from hardware info
function getDeviceId() {
 const raw = `${os.hostname()}-${os.platform()}-${os.cpus()[0]?.model}`;
 return crypto.createHash("sha256").update(raw).digest("hex").slice(0, 32);
}

async function activate(licenseKey) {
 const deviceId = getDeviceId();

 const response = await fetch("https://your-license-server.com/activate", {
 method: "POST",
 headers: { "Content-Type": "application/json" },
 body: JSON.stringify({ licenseKey, deviceId }),
 });

 const result = await response.json();

 if (result.activated) {
 // Save license key locally so user doesn't re-enter it
 saveToLocalStorage("licenseKey", licenseKey);
 console.log(`Activated: ${result.licenseName}`);
 return { success: true, licenseName: result.licenseName };
 } else {
 console.error(`Activation failed: ${result.error}`);
 return { success: false, error: result.error };
 }
}

// On app startup
async function onAppStart() {
 const savedKey = readFromLocalStorage("licenseKey");

 if (savedKey) {
 const result = await activate(savedKey);
 if (result.success) {
 unlockApp(result.licenseName);
 return;
 }
 }

 // Show license key input dialog
 showActivationPrompt();
}
```

### Web App Example (fetch)

```javascript
async function checkLicense(licenseKey) {
 const deviceId = localStorage.getItem("deviceId") || crypto.randomUUID();
 localStorage.setItem("deviceId", deviceId);

 const res = await fetch("https://your-license-server.com/activate", {
 method: "POST",
 headers: { "Content-Type": "application/json" },
 body: JSON.stringify({ licenseKey, deviceId }),
 });

 return res.json();
}
```

---

## Step 7: Handle Edge Cases

### Re-verification

Don't call the 3DIMLI API on every app launch. Instead:

1. **Cache the verification result** on your server with a timestamp.
2. **Re-verify periodically** (e.g., every 24–48 hours) to catch refunds.
3. **Allow offline grace periods.** If your server is unreachable, let the user continue for a set time (e.g., 7 days).

```javascript
// Example: Cache with expiry
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours

async function verifyWithCache(licenseKey) {
 const cached = activations.get(licenseKey);

 if (cached && Date.now() - cached.verifiedAt < CACHE_DURATION) {
 return cached; // Use cached result
 }

 // Re-verify with 3DIMLI
 const response = await fetch("https://www.3dimli.com/api/software/v1/verify", {
 method: "POST",
 headers: {
 "Authorization": `Bearer ${process.env.DIMLI_SELLER_TOKEN}`,
 "Content-Type": "application/json",
 },
 body: JSON.stringify({ key: licenseKey, product_slug: PRODUCT_SLUG }),
 });

 const result = await response.json();

 if (!result.valid) {
 activations.delete(licenseKey); // Revoke (e.g., refunded)
 return null;
 }

 // Update cache
 if (cached) {
 cached.verifiedAt = Date.now();
 }

 return cached;
}
```

### Handling Refunds

When a buyer gets a refund, 3DIMLI's API will return `` for their key. On your next re-verification cycle, revoke access gracefully:

- Show a message: "Your license is no longer valid. Please contact support."
- Don't delete user data, just lock features.

---

## Step 8: Tell Your Buyers Where to Find Their Key

In your software's README and activation dialog, tell buyers:

> **Your license key is your Order Item ID.** You can find it in:
> 1. Your purchase confirmation email from 3DIMLI.
> 2. Your 3DIMLI dashboard → **Orders** → expand the order → **License Product ID**.
> 3. Your 3DIMLI dashboard → **Downloads** → click the license details → **Item ID**.

---

## Test Activation Script

Use this script to test your integration end-to-end. Replace the values in `CONFIG` with your own product slug and a valid license key from a real purchase.

```javascript
/**
 * 3DIMLI Software Activation — Test Script
 *
 * Run: node test-activation.mjs
 *
 * Replace the values in CONFIG with your own before running.
 */

// ─── CONFIG ──────────────────────────────────────────────────────────────────
const CONFIG = {
 baseUrl: "https://www.3dimli.com",
 // Your product slug — the path segment after the domain in the store URL.
 // Example: if your product is at 3dimli.com/software/my-tool, the slug is "/my-tool"
 productSlug: "YOUR_PRODUCT_SLUG_HERE",
 // A license key to test — paste the order item ID from a real completed purchase.
 testKey: "YOUR_LICENSE_KEY_HERE",
 // Your seller license-verification token — generate at:
 // https://www.3dimli.com/dashboard/seller/settings/api-tokens
 // Prefer reading from process.env.DIMLI_SELLER_TOKEN over hardcoding.
 sellerToken: process.env.DIMLI_SELLER_TOKEN ?? "YOUR_SELLER_TOKEN_HERE",
};

// ─────────────────────────────────────────────────────────────────────────────

async function verifyLicenseKey(key, productSlug) {
 const url = `${CONFIG.baseUrl}/api/software/v1/verify`;
 let response;
 try {
 response = await fetch(url, {
 method: "POST",
 headers: {
 "Authorization": `Bearer ${CONFIG.sellerToken}`,
 "Content-Type": "application/json",
 },
 body: JSON.stringify({ key, product_slug: productSlug }),
 });
 } catch (networkError) {
 return { valid: false, error: "Network error — check your internet connection." };
 }

 if (response.status === 401) {
 return { valid: false, error: "Unauthorized — token missing, invalid, or revoked." };
 }

 if (response.status === 403) {
 return { valid: false, error: "Forbidden — token doesn't own this product or your subscription has lapsed." };
 }

 if (response.status === 429) {
 const retryAfter = response.headers.get("Retry-After") ?? "60";
 return { valid: false, error: `Rate limit reached. Retry after ${retryAfter} seconds.` };
 }

 if (!response.ok) {
 return { valid: false, error: `Server error (HTTP ${response.status}).` };
 }

 return await response.json();
}

// ─── TEST CASES ──────────────────────────────────────────────────────────────

async function runTests() {
 console.log("3DIMLI Software Activation Test");
 console.log("================================\n");

 // Test 1: Valid key from CONFIG
 console.log("Test 1 — Valid key (from CONFIG)");
 console.log(` key: ${CONFIG.testKey}`);
 console.log(` product_slug: ${CONFIG.productSlug}`);
 const result1 = await verifyLicenseKey(CONFIG.testKey, CONFIG.productSlug);
 if (result1.valid) {
 console.log(` ✓ VALID — Product: "${result1.productName}", License: "${result1.licenseName ?? "Standard"}"`);
 } else {
 console.log(` ✗ INVALID${result1.error ? " — " + result1.error : ""}`);
 console.log(" (Replace CONFIG values with your real product slug and a valid license key)");
 }
 console.log();

 // Test 2: Malformed key (not a UUID)
 console.log("Test 2 — Malformed key (not a UUID)");
 console.log(" key: not-a-valid-uuid");
 const result2 = await verifyLicenseKey("not-a-valid-uuid", CONFIG.productSlug);
 console.log(result2.valid ? " ✗ valid (unexpected)" : " ✓ correctly rejected as invalid");
 console.log();

 // Test 3: Random UUID (unknown key)
 const randomUuid = "00000000-0000-0000-0000-000000000000";
 console.log("Test 3 — Unknown UUID key");
 console.log(` key: ${randomUuid}`);
 const result3 = await verifyLicenseKey(randomUuid, CONFIG.productSlug);
 console.log(result3.valid ? " ✗ returned valid (unexpected)" : " ✓ correctly returned { valid: false }");
 console.log();

 // Test 4: Valid key against wrong product slug (cross-product check)
 const wrongSlug = "/wrong-product-slug";
 console.log("Test 4 — Valid key used against wrong product_slug (scope check)");
 console.log(` key: ${CONFIG.testKey}`);
 console.log(` product_slug: ${wrongSlug} (wrong product)`);
 const result4 = await verifyLicenseKey(CONFIG.testKey, wrongSlug);
 console.log(
 result4.valid
 ? " ✗ returned valid — scope check failed (report this as a bug)"
 : " ✓ correctly rejected — key is scoped to its original product only"
 );

 console.log("\nDone.");
}

runTests().catch((err) => {
 console.error("Unexpected error:", err);
 process.exit(1);
});
```

---

## Complete Checklist

- [ ] Software product created on 3DIMLI with license tiers defined
- [ ] Product slug identified from your store URL
- [ ] Seller license-verification token generated and stored as a server-side secret
- [ ] License server deployed; software calls your server instead of 3DIMLI directly
- [ ] Seat limits enforced if required
- [ ] Software prompts for license key on first launch
- [ ] Verification results cached on the license server to reduce API calls
- [ ] Re-verification runs periodically (every 24-48h)
- [ ] Offline grace period implemented
- [ ] Refund handling added (revoke on re-verify failure)
- [ ] README includes instructions on where buyers find their license key
- [ ] Tested the full flow: purchase → enter key → activate → use

---

## Related

- [Software Product Type](/creating-products/software). Creating and listing your software product
- [Software License Verification API](/creating-products/software/license-verification-api). Full API reference
- [Games License Integration Guide](/games-license-integration). Equivalent walkthrough for game products
- [Upload Your First Product](/upload-product). General product upload guide
