Skip to main content

How to Add License Verification to Your Software

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 │
│ │ │ │ │ │
│ 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 and select Software.
  2. Fill in the product details (title, description, category, and tags).
  3. Set up your software license tiers. For example:
License TierDescriptionSeatsPrice
Standard LicensePersonal and commercial use1 device$29
Team LicenseUp to 5 users5 devices$99
Company LicenseUnlimited within one organizationUnlimited$299
  1. Upload your software as a compressed archive (ZIP, RAR, 7Z, etc.).
  2. Include a README in the archive with installation instructions and where to enter the license key.
  3. Submit for review.

For the full product creation guide, see Software Product Type.


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/my-tool, your product slug is /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:

POST https://www.3dimli.com/api/software/v1/verify

Request:

{
"key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"product_slug": "/my-tool"
}
FieldWho provides itDescription
keyBuyer (typed into software UI)The license key from their purchase confirmation email
product_slugSeller (hardcoded in software binary)The product's URL slug from the store (e.g., /my-tool)

Response (valid):

{
"valid": true,
"productName": "My Tool",
"licenseName": "Professional"
}

Response (invalid):

{
"valid": false
}

Response (rate limited):

HTTP 429   Retry-After: 45
{ "valid": false }
  • No authentication/API key needed. The endpoint is public and rate-limited (30 requests/minute per IP).
  • The productName is the title of your product on 3DIMLI.
  • The licenseName matches whatever license tier name you set when creating the product.
  • Refunded or incomplete orders return { "valid": false }.
  • The API never leaks information about why a key is invalid — it always returns the same { "valid": false } response.

For the full API reference, see Software License Verification API.


Step 4: Add License Verification to Your Software

The simplest integration calls the 3DIMLI API directly from your software — no license server required. Hardcode your product slug and call the verify endpoint when the user activates.

const PRODUCT_SLUG = "/my-tool"; // never changes, not a secret

async function activateLicense(userKey) {
const res = await fetch("https://www.3dimli.com/api/software/v1/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: userKey, product_slug: PRODUCT_SLUG }),
});
const data = await res.json();

if (res.status === 429) {
showError("Too many attempts. Try again in " + res.headers.get("Retry-After") + "s");
return;
}

if (!data.valid) {
showError("Invalid license key.");
return;
}

// Unlock features based on tier
if (data.licenseName === "Enterprise") enableAllFeatures();
else if (data.licenseName === "Professional") enableProFeatures();
else enableBasicFeatures();

saveLicenseLocally(userKey); // cache so user doesn't re-enter every time
}
tip

This direct approach works for most software. You only need a separate license server if you want to enforce per-seat/per-device limits — see Step 5 below.


Step 5 (Optional): Build a License Server for Seat Enforcement

3DIMLI confirms a purchase happened — it does not enforce how many machines use a key. If your license terms require per-seat limits, build your own license server between your software and 3DIMLI.

Node.js Example (Express)

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

// ─── Configuration ───────────────────────────────────────
const PRODUCT_SLUG = "/my-tool"; // From Step 2

// 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: { "Content-Type": "application/json" },
body: JSON.stringify({ key: licenseKey, product_slug: PRODUCT_SLUG }),
}
);
const result = await response.json();

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)

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

PRODUCT_SLUG = "/my-tool" # From Step 2

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",
json={"key": license_key, "product_slug": PRODUCT_SLUG},
)
result = resp.json()

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 (with License Server)

If you built a license server in Step 5, your software calls your server instead of 3DIMLI directly.

Desktop App Example (JavaScript/Electron)

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)

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).
// 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: { "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 { "valid": false } 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.

/**
* 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/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",
};

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

async function verifyLicenseKey(key, productSlug) {
const url = `${CONFIG.baseUrl}/api/software/v1/verify`;
let response;
try {
response = await fetch(url, {
method: "POST",
headers: { "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 === 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
  • License verification integrated (direct API call or via license server)
  • Seat limits enforced if required (optional — needs a license server)
  • Software prompts for license key on first launch
  • Verification results cached locally 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