> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rmz.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# License Integration

> Add license key verification to your software using the RMZ licensing system.

RMZ provides a complete software licensing system. Customers buy a license key from your store, and your software verifies it with a single API call. This guide covers setup, implementation, and best practices.

## How It Works

<Steps>
  <Step title="Create a license product">
    In your store dashboard, create a product with type **Software License**. Configure the lock type, max activations, and pricing plans.
  </Step>

  <Step title="Customer purchases">
    When a customer buys the product, a license key is automatically generated and delivered (e.g., `MYAPP-XXXX-XXXX-XXXX-XXXX`).
  </Step>

  <Step title="Your software verifies">
    Your application calls the RMZ license verification API with the key. The API validates the key and auto-activates the device.
  </Step>

  <Step title="API responds">
    You receive the license status, plan details, activation count, and expiry information.
  </Step>
</Steps>

## Reference Repository

Full working examples in Python, PHP, JavaScript, and Lua (FiveM) are available at:

```
https://github.com/Rmz-App/rmz-license-examples
```

***

## Step 1: Create a License Product

In your store dashboard, go to **Products > New Product** and configure:

| Setting             | Description                                                                            |
| ------------------- | -------------------------------------------------------------------------------------- |
| **Type**            | Software License                                                                       |
| **Key prefix**      | A short prefix for generated keys (e.g., `MYAPP` produces `MYAPP-XXXX-XXXX-XXXX-XXXX`) |
| **Lock type**       | How keys are bound to devices (see below)                                              |
| **Max activations** | How many devices can use a single key (0 = unlimited)                                  |
| **Plans**           | Duration-based pricing tiers (monthly, yearly, lifetime, etc.)                         |
| **E2EE**            | Optional end-to-end encryption for API responses                                       |

### Lock Types

<CardGroup cols={3}>
  <Card title="none">
    Key-only verification. No device binding. Any device can use the key. Best for simple products.
  </Card>

  <Card title="hwid">
    Locks to a hardware ID (machine fingerprint). Each unique HWID uses an activation slot. Best for desktop software.
  </Card>

  <Card title="ip">
    Locks to the caller's IP address. Each unique IP uses a slot. Best for server-side applications.
  </Card>
</CardGroup>

***

## Step 2: Implement Verification

The verification endpoint validates the key and auto-activates the device in a single call.

```
POST https://license.rmz.gg/verify
```

### Request

| Field         | Type    | Required    | Description                        |
| ------------- | ------- | ----------- | ---------------------------------- |
| `product_id`  | integer | Yes         | Your product ID from the dashboard |
| `license_key` | string  | Yes         | The customer's license key         |
| `hwid`        | string  | Conditional | Required when lock type is `hwid`  |

### Implementation Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  async function verifyLicense(licenseKey, hwid) {
    const response = await fetch("https://license.rmz.gg/verify", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        product_id: YOUR_PRODUCT_ID,
        license_key: licenseKey,
        hwid: hwid // Optional for 'none' and 'ip' lock types
      })
    });

    const result = await response.json();

    if (result.success) {
      console.log("License valid!");
      console.log("Product:", result.data.product.name);
      console.log("Status:", result.data.status);
      console.log("Expires:", result.data.expires_at || "Never (lifetime)");
      console.log("Activations:", `${result.data.activations.current}/${result.data.activations.max || "unlimited"}`);
      return result.data;
    } else {
      console.error("License invalid:", result.error, result.message);
      return null;
    }
  }
  ```

  ```python Python theme={null}
  import requests

  def verify_license(license_key, hwid=None):
      payload = {
          "product_id": YOUR_PRODUCT_ID,
          "license_key": license_key,
      }
      if hwid:
          payload["hwid"] = hwid

      response = requests.post(
          "https://license.rmz.gg/verify",
          json=payload
      )
      result = response.json()

      if result.get("success"):
          data = result["data"]
          print(f"License valid! Product: {data['product']['name']}")
          print(f"Status: {data['status']}")
          print(f"Expires: {data['expires_at'] or 'Never (lifetime)'}")
          return data
      else:
          print(f"License invalid: {result['error']} - {result['message']}")
          return None
  ```

  ```php PHP theme={null}
  <?php

  function verifyLicense(string $licenseKey, ?string $hwid = null): ?array
  {
      $payload = [
          'product_id' => YOUR_PRODUCT_ID,
          'license_key' => $licenseKey,
      ];

      if ($hwid) {
          $payload['hwid'] = $hwid;
      }

      $response = json_decode(file_get_contents(
          'https://license.rmz.gg/verify',
          false,
          stream_context_create([
              'http' => [
                  'method' => 'POST',
                  'header' => 'Content-Type: application/json',
                  'content' => json_encode($payload),
              ]
          ])
      ), true);

      if ($response['success']) {
          echo "License valid! " . $response['data']['product']['name'] . "\n";
          return $response['data'];
      }

      echo "Invalid: " . $response['error'] . "\n";
      return null;
  }
  ```

  ```lua FiveM (Lua) theme={null}
  PerformHttpRequest("https://license.rmz.gg/verify", function(code, text)
      local result = json.decode(text)

      if code == 200 and result.success then
          print("License valid: " .. result.data.product.name)
          print("Status: " .. result.data.status)
          -- Allow resource to start
      else
          print("License invalid: " .. (result.error or "unknown"))
          -- Stop resource
          StopResource(GetCurrentResourceName())
      end
  end, "POST", json.encode({
      product_id = YOUR_PRODUCT_ID,
      license_key = GetConvar("license_key", ""),
      hwid = GetConvar("sv_licenseKeyToken", "")
  }), { ["Content-Type"] = "application/json" })
  ```
</CodeGroup>

### Success Response

```json theme={null}
{
  "success": true,
  "data": {
    "status": "active",
    "lock_type": "hwid",
    "product": {
      "id": 123,
      "name": "My Script Pro"
    },
    "plan": {
      "duration": "annually",
      "duration_label": "سنه",
      "start_date": "23-03-2026",
      "end_date": "23-03-2027",
      "is_active": true
    },
    "expires_at": "2027-03-23T12:00:00.000000Z",
    "expires_in_days": 365,
    "activations": {
      "current": 1,
      "max": 3,
      "remaining": 2
    },
    "metadata": null
  }
}
```

### Error Codes

| Code                | HTTP Status | Description                                    |
| ------------------- | ----------- | ---------------------------------------------- |
| `LICENSE_NOT_FOUND` | 404         | Key does not exist for this product            |
| `LICENSE_EXPIRED`   | 403         | License has passed its expiry date             |
| `LICENSE_REVOKED`   | 403         | Permanently revoked by the store owner         |
| `LICENSE_SUSPENDED` | 403         | Temporarily suspended                          |
| `HWID_REQUIRED`     | 403         | Lock type is `hwid` but no `hwid` was provided |
| `ACTIVATION_LIMIT`  | 429         | All device activation slots are used           |

***

## Step 3: Generate Hardware IDs

For `hwid` lock type, generate a consistent machine fingerprint by combining hardware-specific values:

```javascript theme={null}
// Node.js example
const crypto = require("crypto");
const os = require("os");

function generateHWID() {
  const components = [
    os.hostname(),
    os.cpus()[0]?.model,
    os.totalmem().toString(),
    os.platform(),
    os.arch()
  ];

  return crypto
    .createHash("sha256")
    .update(components.join("|"))
    .digest("hex");
}
```

<Tip>
  Choose hardware components that are stable across reboots but unique per machine. Avoid components that change frequently (like available memory or uptime).
</Tip>

***

## Step 4: Handle E2EE (Optional)

When E2EE is enabled on your license product, all API responses are encrypted with AES-256-GCM. This prevents response tampering and man-in-the-middle spoofing.

### Encrypted Response Format

```json theme={null}
{
  "encrypted": true,
  "payload": "base64-encoded-ciphertext",
  "nonce": "base64-encoded-12-byte-nonce",
  "tag": "base64-encoded-16-byte-auth-tag"
}
```

### Decryption

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function decryptResponse(encrypted, encryptionKey) {
    const key = Buffer.from(encryptionKey, "hex"); // 64 hex chars = 32 bytes
    const nonce = Buffer.from(encrypted.nonce, "base64");
    const tag = Buffer.from(encrypted.tag, "base64");
    const ciphertext = Buffer.from(encrypted.payload, "base64");

    const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
    decipher.setAuthTag(tag);

    const decrypted = Buffer.concat([
      decipher.update(ciphertext),
      decipher.final()
    ]);

    return JSON.parse(decrypted.toString("utf8"));
  }
  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
  import base64, json

  def decrypt_response(encrypted, encryption_key):
      key = bytes.fromhex(encryption_key)  # 64 hex chars = 32 bytes
      nonce = base64.b64decode(encrypted["nonce"])
      tag = base64.b64decode(encrypted["tag"])
      ciphertext = base64.b64decode(encrypted["payload"])

      aesgcm = AESGCM(key)
      plaintext = aesgcm.decrypt(nonce, ciphertext + tag, None)

      return json.loads(plaintext.decode("utf-8"))
  ```

  ```php PHP theme={null}
  function decryptResponse(array $encrypted, string $encryptionKey): array
  {
      $key = hex2bin($encryptionKey); // 64 hex chars = 32 bytes
      $nonce = base64_decode($encrypted['nonce']);
      $tag = base64_decode($encrypted['tag']);
      $ciphertext = base64_decode($encrypted['payload']);

      $plaintext = openssl_decrypt(
          $ciphertext,
          'aes-256-gcm',
          $key,
          OPENSSL_RAW_DATA,
          $nonce,
          $tag
      );

      return json_decode($plaintext, true);
  }
  ```
</CodeGroup>

<Warning>
  Never hardcode the encryption key as a plain string in your source code. Derive it at runtime from multiple values or use an obfuscation layer.
</Warning>

***

## Best Practices

### Verification Frequency

* Verify the license **once on application startup**
* Optionally re-verify periodically (e.g., every 24 hours) for subscription products
* Cache the verification result locally to handle temporary network outages
* **Rate limit:** The API allows 60 requests per minute per IP

### Security Recommendations

1. **Use HWID lock type** for desktop applications to prevent key sharing
2. **Enable E2EE** to prevent response tampering (someone injecting `"success": true` via a local proxy)
3. **Obfuscate your code** to make it harder to find and bypass the verification logic
4. **Embed verification deep** in your application flow, not in a single easily-patchable function
5. **Combine with integrity checks** to detect binary modification
6. **Do not store the encryption key** as a visible string literal

### Graceful Degradation

Handle network failures gracefully:

```javascript theme={null}
async function verifyWithFallback(licenseKey, hwid) {
  try {
    const result = await verifyLicense(licenseKey, hwid);
    if (result) {
      // Cache the result locally
      saveToCache(licenseKey, result);
    }
    return result;
  } catch (networkError) {
    // Network failure — check local cache
    const cached = loadFromCache(licenseKey);
    if (cached && cached.status === "active") {
      console.log("Using cached license validation");
      return cached;
    }
    // No cache available — block or allow with warning
    return null;
  }
}
```

***

## Managing Licenses in the Dashboard

From your store dashboard under **License Keys**, you can:

* View all issued licenses with search and filters
* Create manual licenses (for testing, promotional use)
* Revoke, suspend, or reactivate individual licenses
* Reset device activations (frees all slots)
* View activation history and verification logs

### License Lifecycle

| Event              | Effect                                  |
| ------------------ | --------------------------------------- |
| Customer purchases | Key auto-generated and delivered        |
| Order completed    | License activated, expiry set from plan |
| Order cancelled    | All order licenses revoked              |
| Order refunded     | All order licenses revoked              |
| Expiry date passes | Marked expired (checked hourly)         |

<Note>
  For complete API reference details, see the [Licensing documentation](/licensing/overview).
</Note>
