# Luarmor API Documentation

{% hint style="danger" %}
⚠️⚠️ ! IMPORTANT ! ⚠️⚠️

Luarmor http API is not a public API. It's accessible by Luarmor customers and script developers only.

<mark style="color:red;">**👉 You must whitelist your server's IP address 👈**</mark> on [**luarmor dashboard**](https://luarmor.net/profile) in order to make API calls. Otherwise, your requests will be blocked by cloudflare.
{% endhint %}

## Top things to always remember: ✅

{% hint style="warning" %} <mark style="color:blue;">`Content-Type`</mark> header must be set to <mark style="color:blue;">`application/json`</mark> in all of your requests
{% endhint %}

{% hint style="warning" %}
Each endpoint has it's own ratelimits, but in general you can make 60 requests **per minute**. Exceeding this limit will result a <mark style="color:red;">`429`</mark>
{% endhint %}

{% hint style="info" %}
**Parameter Types:**

<mark style="color:green;">**Path**</mark><mark style="color:green;">:</mark> `https://site.com/data/`<mark style="color:red;">`PARAMETER_HERE`</mark>`/stuff`

<mark style="color:green;">**Query:**</mark> `https://site.com/data/things/stuff`<mark style="color:red;">`?paramName=PARAMETER_HERE`</mark>

<mark style="color:green;">**Body:**</mark> (In Request body): <mark style="color:red;">`{ "fieldName": "PARAMETER_HERE" }`</mark>

<mark style="color:green;">**Header:**</mark> (In Request headers): <mark style="color:red;">`{ "headerName": "HEADER VALUE" }`</mark>
{% endhint %}

## API/Key Management

### Getting API status

<mark style="color:blue;">`GET`</mark> `https://api.luarmor.net/status`

This will return you the version information about the API.

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "version": "v3",
    "active": true,
    "message": "API is up and working!",
    "warning": false,
    "warning_message": "No warning"
}
```

{% endtab %}
{% endtabs %}

### Getting API key details

<mark style="color:blue;">`GET`</mark> `https://api.luarmor.net/v3/keys/:api_key/details`

You can get details of your API key. Project/Script IDs, execution amounts, script names etc..

Example URL: `https://api.luarmor.net/v3/keys/`<mark style="color:green;">`300058fb1987f0e019b0c980ad01`</mark>`/details`

#### Path Parameters

| Name                                       | Type   | Description               |
| ------------------------------------------ | ------ | ------------------------- |
| api\_key<mark style="color:red;">\*</mark> | String | API key to get details of |

{% tabs %}
{% tab title="200: OK Success" %}

```javascript
{
    "success": true,
    "message": "Success!",
    "email": "federal@fbi.gov",
    "discord_id": "493827492379239857",
    "expires_at": 1674606328,
    "registered_at": 1672014328,
    "plan": "r",
    "enabled": 1,
    "projects": [
        {
            "platform": "roblox",
            "id": "2923c450865b60f65a5d06bb39ff1335",
            "name": "Alka-Seltzer Hub",
            "settings": {
                "reset_hwid_cooldown": -1
            },
            "scripts": [
                {
                    "script_name": "server crasher (free)",
                    "script_id": "ef67bc7f7bd0948f4fff336274a4a345",
                    "script_version": "0008",
                    "ffa": true,
                    "silent": false
                },
                {
                    "script_name": "premium one",
                    "script_id": "df3a1b7d79ceb911f8ffa762b0bd0596",
                    "script_version": "0000",
                    "ffa": false,
                    "silent": false
                }
            ]
        }
    ]
}
```

{% endtab %}

{% tab title="403: Forbidden Incorrect API key" %}

```javascript
{
    "success": false,
    "message": "Invalid API key! Visit https://luarmor.net/ to get access."
}
```

API key is no longer valid.
{% endtab %}

{% tab title="400: Bad Request Invalid API key" %}

```javascript
{
    "success": false,
    "message": "Wrong API key"
}
```

{% endtab %}
{% endtabs %}

### Getting API key stats

<mark style="color:blue;">`GET`</mark> `https://api.luarmor.net/v3/keys/:api_key/stats`

You can fetch the stats of your API key. This includes usage details, remaining obfuscations, max obfuscations, max users, execution amounts, monthly execution graph values etc..

Example URL: `https://api.luarmor.net/v3/keys/`<mark style="color:green;">`300058fb1987f0e019b0c980ad01`</mark>`/stats?noUsers=true`

#### Query Parameters

| Name    | Type    | Description                                                                                                                                                                                                                                       |
| ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| noUsers | Boolean | <p>If "true", it will return the info about user limits. (e.g how many users there are, # of banned, # of whitelisted)</p><p>This parameter is optional, you don't have to include it at all. If included, server might respond 0.001s faster</p> |

{% tabs %}
{% tab title="200: OK Success" %}

```javascript
{
    "success": true,
    "message": "Success!",
    "execution_data": {
        "frequency": 86400,
        "executions": [
            5, 20, 0, 0, 0, 0, 0, 1900
        ]
    },
    "stats": {
        "obfuscations": 10,
        "scripts": 3,
        "users": 3,
        "attacks_blocked": 0,
        "default": {
            "scripts": 18,
            "users": 10000,
            "obfuscations": 3000
        },
        "reset_at": 1672014328
    }
}
```

{% endtab %}
{% endtabs %}

## User & Key Management

### Basics

{% hint style="warning" %}
Every user you add by making POST requests will automatically have a random "key" generated specifically for that user. If you don't provide the details about the user (such as hwid, discord id), key will still be generated.
{% endhint %}

{% hint style="success" %}
If there's <mark style="color:red;">**no HWID**</mark> assigned for the key, it will <mark style="color:green;">automatically get assigned</mark> when user executes the script with `script_key = "key here";` on top of their script.

If there's <mark style="color:red;">**no Discord ID**</mark> assigned for the key, user can <mark style="color:green;">claim the key</mark> by using the discord bot's "Redeem" button. Once their discord ID is linked to the key, user will be able to click on "Reset HWID" button on discord bot if it's enabled in project settings.
{% endhint %}

{% hint style="danger" %}
User **must add** `script_key = "KEY HERE";` on top of the loader script. Otherwise it will not work unless FFA mode is on.&#x20;
{% endhint %}

{% hint style="warning" %}
Each project has it's own separate database for user/key pairs. If a user is whitelisted in a project, they will be automatically able to <mark style="color:yellow;">run all scripts</mark> created inside that project. This is also known as "script hub logic"
{% endhint %}

{% hint style="success" %}
You can set **custom notes** for each client by including `note` field in your request body. It will make it easier do identify the user.
{% endhint %}

### Expiry dates & Time limited keys:

{% hint style="success" %}
There are 2 ways to restrict a key based on the time:

1\) **key\_days**\
2\) **auth\_expire**\
\
The difference is that "**`key_days`**" indicates the number of days a key will have once it has been redeemed or executed for the very first time. This allows you to generate unused time-locked keys without their timer starting right away so you can stock them.\
\
Meanwhile "`auth_expire`" is the actual timestamp of the expiry date. If you generate keys via key\_days, once that key has been activated, it will automatically adjust `auth_expire` according to current time + (`key_days` \* 86400).\
\
If you don't provide key\_days and provide `auth_expire` directly, you must include one of **`identifier`** or **`discord_id`** parameters to tell the server that it is a claimed key, so it will start counting towards their remaining days instantly. If you don't provide identifier or discord\_id fields, it will automatically convert the offset between current time and auth\_expire to key\_days.
{% endhint %}

{% hint style="info" %}
You can pre-define `discord_id / identifier` values while generating a key. If you don't provide them, they will get automatically assigned when user runs the script or uses "Redeem" button on discord bot interface.
{% endhint %}

### Creating a key / user

<mark style="color:green;">`POST`</mark> `https://api.luarmor.net/v3/projects/:project_id/users`

This endpoint will generate a key. If you don't specify the parameters, key will be 'unassigned' which means a user with the key can claim it and automatically assign their HWID / Discord ID to the key.

Users who have their HWIDs linked to the keys will be able to run the script. If they don't include `script_key` on top of their script, they will not be able to run the script as long as the FFA mode isn't on.

#### Path Parameters

| Name                                          | Type   | Description                                             |
| --------------------------------------------- | ------ | ------------------------------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | ID of the project that you want to add the user key to. |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API key.    |

#### Request Body

| Name                     | Type   | Description                                                                                                                                                                                                  |
| ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| identifier \<optional>   | String | Identifier of the user to whitelist. Could be a HWID.                                                                                                                                                        |
| auth\_expire \<optional> | Int32  | <p>Unix timestamp (seconds) of expiry date. If you don't provide this field, it will never expire.</p><p>Read <a href="#expiry-dates-and-time-limited-keys">here </a>for more info.</p>                      |
| note \<optional>         | String | Custom note for client. This might make it easier to identify the user.                                                                                                                                      |
| discord\_id \<optional>  | String | Discord ID of the user. If not specified, user won't be able to resethwid on their own. They can still link their discord id to their key using Redeem button on the discord bot (if you configured the bot) |
| key\_days \<optional>    | Number | Number of days a key will have once it has been activated by user. Read [here ](#expiry-dates-and-time-limited-keys)for more info.                                                                           |

{% tabs %}
{% tab title="200: OK Key has been added successfully" %}

```javascript
{
    "success": true,
    "message": "Success!",
    "user_key": "awIUiHenZzfScOsqkXwGHyRAOsTTcPMR"
}
```

{% endtab %}

{% tab title="400: Bad Request Bad request" %}

```javascript
{
    "success": false,
    "message": "Discord ID already exist."
}
```

{% endtab %}

{% tab title="403: Forbidden Invalid API key" %}

{% endtab %}
{% endtabs %}

### Updating an existing user

<mark style="color:purple;">`PATCH`</mark> `https://api.luarmor.net/v3/projects/:project_id/users`

You can use this endpoint to edit an already existing user. If you don't provide a specific field, API will assume that you don't want to change that property, so it's going to stay the same.&#x20;

#### Path Parameters

| Name                                          | Type   | Description                            |
| --------------------------------------------- | ------ | -------------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | ID of the project that user belongs to |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API Key     |

#### Request Body

| Name                                        | Type   | Description                                                                                              |
| ------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------- |
| identifier \<optional>                      | String | Identifier of the user to whitelist. Could be a HWID.                                                    |
| auth\_expire \<optional>                    | Int32  | Unix timestamp (seconds) of expiry date. If you don't want it to expire, use negative one (-1) as value. |
| note \<optional>                            | String | Custom note for client. This might make it easier to identify the user.                                  |
| discord\_id \<optional>                     | String | Discord ID of the user.                                                                                  |
| user\_key<mark style="color:red;">\*</mark> | String | Unique user\_key to edit.                                                                                |

{% tabs %}
{% tab title="200: OK Edited successfully" %}

```javascript
{
    "success": true,
    "message": "Success!"
}
```

{% endtab %}

{% tab title="400: Bad Request " %}

{% endtab %}

{% tab title="403: Forbidden " %}

{% endtab %}
{% endtabs %}

### Deleting the key

<mark style="color:red;">`DELETE`</mark> `https://api.luarmor.net/v3/projects/:project_id/users`

You can delete a key from your script, this will also remove the access of the user who has their hwid/discord id linked to that key.

Example URL: `https://api.luarmor.net/v3/projects/`<mark style="color:green;">`3f98888f9999c9999e99a2`</mark>`/users?user_key=n1gG3Rs3CkmYd1Ck64gg0t`

#### Path Parameters

| Name                                          | Type   | Description                                    |
| --------------------------------------------- | ------ | ---------------------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | ID of the project to remove user's access from |

#### Query Parameters

| Name                                        | Type   | Description   |
| ------------------------------------------- | ------ | ------------- |
| user\_key<mark style="color:red;">\*</mark> | String | Key to delete |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API Key     |

{% tabs %}
{% tab title="200: OK Key has been deleted" %}

```javascript
{ 
    "success": true, 
    "message": "User has been deleted!" 
}
```

{% endtab %}

{% tab title="404: Not Found Wrong project id / user key" %}

```javascript
{ "success": false, "message": "Key not found" }
```

{% endtab %}

{% tab title="403: Forbidden Invalid API key" %}

{% endtab %}
{% endtabs %}

### Getting users

<mark style="color:blue;">`GET`</mark> `https://api.luarmor.net/v3/projects/:project_id/users`

You can fetch all users from a script, and you can specify filters too. (such as discord\_id, identifier etc.) If you want to get someone's user\_key from their discord\_id, the key must have it linked first.

Note that response body will be an object array containing users. If you specified a filter value (e.g discord\_id=124345) the array will contain one user so you just have to read users\[0];

#### Query Parameters

| Name                    | Type   | Description                                                                                                                                              |
| ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| discord\_id \<optional> | String | Discord ID to get the connected user                                                                                                                     |
| user\_key \<optional>   | String | Key to get the connected user                                                                                                                            |
| identifier \<opitonal>  | String | HWID to get the connected user                                                                                                                           |
| from \<optional>        | Number | Start offset of the results. Useful for paging users and not fetching all at once.                                                                       |
| until \<optional>       | Number | Finish index of the results. Useful for limiting the results to a specific range between "from" and "until" parameters.                                  |
| search \<optional>      | String | Useful for filtering results that contains the value of "search" anywhere in one of their "identifier", "user\_key", "discord\_id" or "note" properties. |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API key     |

{% tabs %}
{% tab title="200: OK Success" %}
**Note:** Each user will have a 'status' field. It can be either "active", "reset", or "banned".\
**`active`** means user has linked their hwid to key and its <mark style="color:green;">**active**</mark>.\
**`reset`** means user has reset their hwid and it's <mark style="color:green;">**waiting to be assigned**</mark> upon first execution\
`banned` means user doesn't have a key linked (aka. **unknown** ) and banned.\
\
If status equals to "active" but at the same time "banned" field is true, it means user **has a key linked** but still banned. Read examples below

```javascript
{
    "success": true,
    "message": "Success!",
    "users": [
        {
            "user_key": "zZAaGyLhHiwCJHYewdRFmzBLKYCfJBek",
            "identifier": "hwid will be here if linked",
            "identifier_type": "HWID",
            "discord_id": "",
            "status": "active",
            "last_reset": 1672016017,
            "total_resets": 3,
            "auth_expire": -1,
            "banned": 0,
            "ban_reason": "",
            "ban_expire": 0,
            "unban_token": "",
            "total_executions": 10,
            "note": "",
            "ban_ip": ""
        },
        {
            "user_key": "ktwCSBSgmWtlTrignkPB3hglhqLyjqKm",
            "identifier": "",
            "identifier_type": "HWID",
            "discord_id": "4157951988959",
            "status": "reset",
            "last_reset": 1672038004,
            "total_resets": 0,
            "auth_expire": -1,
            "banned": 0,
            "ban_reason": "",
            "ban_expire": 0,
            "unban_token": "",
            "total_executions": 0,
            "note": "",
            "ban_ip": ""
        },
        {
            "user_key": "",
            "identifier": "",
            "identifier_type": "",
            "discord_id": "",
            "status": "banned",
            "last_reset": 0,
            "total_resets": 0,
            "auth_expire": 0,
            "banned": 1,
            "ban_reason": "Invalid signature",
            "ban_expire": 1672629163,
            "unban_token": "QvLJPYzdjBujMlkwembfPrwjIfpsnTkq",
            "total_executions": 0,
            "note": "",
            "ban_ip": "129.20.20.20"
        }
    ]
}
```

{% endtab %}

{% tab title="403: Forbidden Invalid API key" %}

{% endtab %}

{% tab title="404: Not Found Project not found" %}

```javascript
{
    "success": false,
    "message": "Project not found!"
}
```

{% endtab %}
{% endtabs %}

### Resetting the HWID of a key

<mark style="color:green;">`POST`</mark> `https://api.luarmor.net/v3/projects/:project_id/users/resethwid`

#### Path Parameters

| Name                                          | Type   | Description                      |
| --------------------------------------------- | ------ | -------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | Project ID that contains the key |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API Key     |

#### Request Body

| Name                                        | Type    | Description                                                                        |
| ------------------------------------------- | ------- | ---------------------------------------------------------------------------------- |
| user\_key<mark style="color:red;">\*</mark> | String  | Key to reset the hwid of.                                                          |
| force \<optional>                           | Boolean | Whether reset HWID is forced or not. If "true", it will ignore resethwid cooldown. |

{% tabs %}
{% tab title="200: OK Success!" %}

```javascript
{
    "success": true,
    "message": "Successfully reset!"
}
```

{% endtab %}

{% tab title="400: Bad Request Bad request" %}
There are few reasons you might be getting 400 error.\
\- **User reset their hwid too frequently**. You can use `"force":true` parameter to bypass this\
\- **Reset hwid is disabled** for this project. You can force it\
\- **User is banned.** In this case, you need to unban it first.

```javascript
{
    "success": false,
    "message": "User is on cooldown."
}
```

{% endtab %}

{% tab title="404: Not Found Key or project not found" %}

```javascript
{
    "success": false,
    "message": "User key doesn't exist"
}
```

{% endtab %}
{% endtabs %}

### Linking discord ID to a key

<mark style="color:green;">`POST`</mark> `https://api.luarmor.net/v3/projects/:project_id/users/linkdiscord`

#### Path Parameters

| Name                                          | Type   | Description                      |
| --------------------------------------------- | ------ | -------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | Project ID that contains the key |

#### Headers

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API Key     |

#### Request Body

| Name                                        | Type    | Description                                                                       |
| ------------------------------------------- | ------- | --------------------------------------------------------------------------------- |
| user\_key<mark style="color:red;">\*</mark> | String  | Key to link the discord ID                                                        |
| discord\_id                                 | String  | Discord ID (1234578635849)                                                        |
| force \<optional>                           | Boolean | If true, it will overwrite the discord ID if key already has one linked. Optional |

{% tabs %}
{% tab title="200: OK Success" %}

```javascript
{
    "success": true,
    "message": "Success!"
}
```

{% endtab %}

{% tab title="400: Bad Request Bad request" %}
There are few reasons you might be getting 400 error.\
\- **There's already a discord ID linked to this key**. You can use `"force":true` parameter to bypass this\
\- **There's another key that's linked to same discord ID.** In this case, you need to delete the other key.\
\- **Invalid discord ID.**

```javascript
{
    "success": false,
    "message": "This key already has a discord linked to it"
}
```

{% endtab %}
{% endtabs %}

### Blacklisting a key

<mark style="color:green;">`POST`</mark> `https://api.luarmor.net/v3/projects/:project_id/users/blacklist`

This will blacklist an existing key, and the HWID linked to it (if any).&#x20;

**Path parameters**

| Name                                          | Type   | Description                      |
| --------------------------------------------- | ------ | -------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | Project ID that contains the key |

**Headers**

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API Key     |

**Request Body**

| Name                                        | Type   | Description                                                                          |
| ------------------------------------------- | ------ | ------------------------------------------------------------------------------------ |
| user\_key<mark style="color:red;">\*</mark> | String | User key to blacklist.                                                               |
| ban\_reason                                 | String | Blacklist reason. Will be shown to user when executed                                |
| ban\_expire                                 | Int32  | Exact unix timestamp of the ban expiry date. Leave -1 or undefined for infinite ban. |

### Unblacklisting a key

<mark style="color:green;">`GET`</mark> `https://api.luarmor.net/v3/projects/:project_id/users/unban`

This endpoint doesn't need strict API key authentication. All you need is an "unban\_token" of the key, it is a 32 character random string assigned automatically when you blacklist someone. It changes every time a key is blacklisted and is unique. You can find it in the user objects in the response body of "[Getting users](#getting-users)" endpoint.

You can simply make any kind of GET request, or visit the URL on your browser. It should look like this:\
`https://api.luarmor.net/v3/projects/123456abcdef/users/unban?unban_token=Xpsy9wegDpA5XS`

**Query Parameters**

| Name                                           | Type   | Description  |
| ---------------------------------------------- | ------ | ------------ |
| unban\_token<mark style="color:red;">\*</mark> | String | Unban token. |

## Script Management

You can programmatically edit scripts using these endpoints

### Updating a script

<mark style="color:green;">`PUT`</mark> `https://api.luarmor.net/v3/projects/:project_id/scripts/:script_id`

**Path parameters**

| Name                                          | Type   | Description                         |
| --------------------------------------------- | ------ | ----------------------------------- |
| project\_id<mark style="color:red;">\*</mark> | String | Project ID that contains the script |
| script\_id<mark style="color:red;">\*</mark>  | String | Script ID to edit                   |

**Headers**

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| Authorization<mark style="color:red;">\*</mark> | String | API key     |

**Request Body**

| Name                                     | Type    | Description    |
| ---------------------------------------- | ------- | -------------- |
| script<mark style="color:red;">\*</mark> | String  | Raw script     |
| silent                                   | Boolean | Silent mode    |
| ffa                                      | Boolean | FFA mode       |
| heartbeat                                | Boolean | Heartbeat      |
| lightning                                | Boolean | Lightning mode |


# Luarmor User Manual & F.A.Q

We documented every single Luarmor feature in this page. If you want to know more about the service you're using, read this documentation.

{% hint style="success" %}
You can also use this documentation below, maintained by Stefanuk. :arrow\_down\_small:

<https://luarmor.mintlify.app/introduction>

:point\_up:
{% endhint %}

## ✅Quickstart Guide✅

If you just started using Luarmor, you should read this quick start guide. It will take you only 10 minutes to understand how Luarmor works.&#x20;

### 1️⃣ Create an account:

* In order to use Luarmor, you need to create an account. And for that, you need an "invite code". Invite codes can be purchased here --> [**https://luarmor.net#prices** ](<https://luarmor.net#prices >)
* Alternatively, you can use an invite code to extend your membership for +30 extra days here    --> [**https://luarmor.net/profile**](https://luarmor.net/profile)

<div><figure><img src="/files/C5IEUXZjQE20XbXXMZn4" alt=""><figcaption><p>Step 1 - Sign Up at <a href="https://luarmor.net/signup">https://luarmor.net/signup</a></p></figcaption></figure> <figure><img src="/files/CAAlsN6xT3IVGvAqhgjb" alt=""><figcaption><p>Step 2 - Receive your login credential ('API Key')</p></figcaption></figure> <figure><img src="/files/DfLfk4NhgqETbqebE4w2" alt=""><figcaption><p>Step 3 - Log in and access the dashboard at<a href="https://luarmor.net/login"> https://luarmor.net/login</a></p></figcaption></figure></div>

### 2️⃣ Upload your script:

{% hint style="info" %}
Luarmor has a feature called **project folders.** They can contain multiple scripts, allowing you to have a **script hub** with multiple games in it.
{% endhint %}

<div><figure><img src="/files/5kOkixyMOOUhxovidber" alt=""><figcaption><p>Step 4 - Create a project at <a href="https://luarmor.net/projects">https://luarmor.net/projects</a></p></figcaption></figure> <figure><img src="/files/SG9WABt2sT32JonOZXlk" alt=""><figcaption><p>Step 5 - Enter project details</p></figcaption></figure> <figure><img src="/files/wdZCVMD07hFdjO3xDnr3" alt=""><figcaption><p>Step 6 - Upload your script to the project</p></figcaption></figure></div>

<div><figure><img src="/files/PNi5kpAts54Xh0WVRMfT" alt=""><figcaption><p>Step 7 - Enter script details. Detailed descriptions can be found below</p></figcaption></figure> <figure><img src="/files/LFF6NR21leelbiMjQKbq" alt=""><figcaption><p>Step 8 - Download the loader file. Keep in mind that loader does not change, so you don't have to re-download it every time you update your script.</p></figcaption></figure></div>

### 3️⃣ Whitelist users:

<mark style="color:green;">**If you are migrating**</mark> from another whitelist service, or your own, you can easily import your users with 2 clicks. Skip to [**this part**](#mass-whitelist-import-users) for mass whitelisting details.

{% hint style="info" %}
There are **2 ways** to whitelist someone:

* Generate an empty key at [**https://luarmor.net/users**](https://luarmor.net/users) and give it to user. Alternatively, you can mass-generate keys and put them to your sellix / shoppy as "**serials"** for automated purchases.&#x20;

<img src="/files/ZhFXqtRmWP2UxKAQ0cLo" alt="" data-size="original">![Sellix / Shoppy integration](/files/izeWe2FJx2hFNeI86fWP)

* Or run `/whitelist` command via discord bot and user will be able to click on "get script" button on control panel

&#x20;![](/files/oBqRS7rtPepDwASqm2v6)![](/files/Dgh3fXrGEsnDzhtcW0J6)

( Bot invite will be DM'ed upon purchasing a Luarmor invite code. Join our discord server to buy an invite code: [**https://discord.gg/luarmor**](https://discord.gg/luarmor) )

{% endhint %}

Once the user is whitelisted, they can execute the script by adding **`script_key = "KEY HERE";`** on top of the script. Note that keys are **linked to user's HWID** and sharing the key with someone else won't work because they will have a different HWID.&#x20;

<figure><img src="/files/jRv7zuxXlMqCp8VmYgm3" alt=""><figcaption><p>Tada! authenticated in 0.7 seconds ;D</p></figcaption></figure>

<div><figure><img src="/files/tpX7T5KMnh6sp2T3VkHL" alt=""><figcaption><p>You will get this notification to your webhook.</p></figcaption></figure> <figure><img src="/files/o5baHISqOoT3XnqSnP4E" alt=""><figcaption><p>script_key must be added on top of the script (except for FFA mode)</p></figcaption></figure></div>

### 4️⃣ Reset HWID:

In some cases, user's HWID might change on its own, when that happens, they must run **`/resethwid`** via discord bot and re-execute the script. It will automatically assign the new HWID upon execution.

<figure><img src="/files/EPAnVNOXU4QIKVESDquW" alt=""><figcaption><p>You can reset HWID like this. Users can reset their own HWIDs as well if you have the setting enabled.</p></figcaption></figure>

If you think someone might be sharing their key and using **`/resethwid`** for others, simply compare action fingerprint in the resethwid notification

<figure><img src="/files/4THpjZ9PbpzsUgYIScsq" alt=""><figcaption><p>Reset HWID notification action fingerprints. If they're *mostly* similar, it's legit. If they're fully different, there's a high chance that user is sharing his key to someone else and /resethwid'ing on their behalf.</p></figcaption></figure>

## ⚡Runtime Variables⚡

Luarmor has runtime variables that allows you to access to user details such as discord id, total executions, script name, premium, user note etc..

They can be used for a lot of things. Check out this example:

<figure><img src="/files/NczDVDmMuduclaaS6IDR" alt=""><figcaption><p>A script that utilizes runtime assigned variables.</p></figcaption></figure>

{% hint style="success" %}
You can see a full list of runtime variables here:

* **`LRM_IsUserPremium`** : if user is whitelisted or not. useful for FFA scripts, ('freemium')&#x20;
* **`LRM_LinkedDiscordID`** : linked discord id&#x20;
* **`LRM_ScriptName`** : name of the current script.&#x20;
* **`LRM_TotalExecutions`** : total executions of the user. it will be 0 by default.&#x20;
* **`LRM_SecondsLeft`** : seconds left until expiry. it will be math.huge if auth\_expire isn't set&#x20;
* **`LRM_UserNote`** : user note ('Not specified' by default)
* **LRM\_ScriptVersion**    : version of the script. Looks like "0.0.0.3" and is a string.
  {% endhint %}

## ⚙️ Discord Bot Configuration ⚙️

{% hint style="danger" %}
This documentation shows the setup for OLD BOT!. New bot looks different than this. It doesn't require a documentation to use the new bot.
{% endhint %}

You can automate everything using this discord bot. Or you can write your own bot using our [API documentation.](/)&#x20;

If you're a Luarmor buyer, <mark style="color:green;">**discord bot invite will be sent to you via DM**</mark>. After inviting the bot, run **`/login`** command and link the server.

<div><figure><img src="/files/hSYGf2Qsl7akvJwfUgNW" alt=""><figcaption><p>Step 1 - Run /login [api key] command</p></figcaption></figure> <figure><img src="/files/jTBqnsaYinVIGQVpAMZe" alt=""><figcaption><p>Step 2 - Set a manager role. People with manager role can run basic management commands like /whitelist, /force-resethwid</p></figcaption></figure></div>

<div><figure><img src="/files/JWFMK7HzpRdbhqd3xxDi" alt=""><figcaption><p>Step 3 - Select a project. Once you do this, all management commands will be <br>applied to this project. </p></figcaption></figure> <figure><img src="/files/g7M5MZGgZ1TRK0AeOG75" alt=""><figcaption><p>Step 4 - Set a buyer role. This role will be automatically<br>assigned when a manager runs /whitelist [user] <br>or when a user runs /redeem [code] or /getrole</p></figcaption></figure></div>

<div><figure><img src="/files/DxNjN4FXdOm3RcBBdn6w" alt=""><figcaption><p>Step 5 - Set the script to DM to users when they run /script or a manager runs /whitelist</p></figcaption></figure> <figure><img src="/files/me8KjGTsgFiZwn13VIfO" alt=""><figcaption><p>This is what the file should be like.</p></figcaption></figure></div>

## 👑 Mass Whitelist - Import Users 👑

If you already have users with a specific role, you can easily mass whitelist every single one of them with one command.&#x20;

<div><figure><img src="/files/GPEmL6uuRKp4juW0bBfC" alt=""><figcaption><p>Your already existing users</p></figcaption></figure> <figure><img src="/files/vfR8O6BzU0qSTzPikt8A" alt=""><figcaption><p>Command syntax</p></figcaption></figure></div>

<figure><img src="/files/ARDKI1DEezuVyZu0volg" alt=""><figcaption><p>Successfully imported users</p></figcaption></figure>

{% hint style="warning" %}
"Total failed" means their discord IDs are already registered, so in order to avoid duplicated entries, it will skip them.
{% endhint %}

**Alternatively**, you can import them as a JSON file.&#x20;

<div><figure><img src="/files/ccEHpecWwywhP7IXGjRD" alt=""><figcaption><p>Step 1 - Go to <a href="https://luarmor.net/users">https://luarmor.net/users</a> and click on gear icon</p></figcaption></figure> <figure><img src="/files/GQKovcTfJ7RB2cPsImDf" alt=""><figcaption><p>Step 2 - Upload your JSON file and confirm. That's it.</p></figcaption></figure></div>

{% hint style="success" %}
Once users are imported, all they need to do is to click on the "Get Script" button and the bot will respond to them with the script + key appended on top.&#x20;
{% endhint %}

## 📜 LRM\_INIT\_SCRIPT macro

This macro can be used to run a function **before** the obfuscated code. It can be useful for displaying a "Enter a key" GUI or running AC bypasses, or anything that must be executed before authentication.

{% hint style="danger" %}
Function passed into this macro will **not** be obfuscated, and **people can view it** as if it is raw. So don't pass critical code here.
{% endhint %}

**Example usage:**

{% code lineNumbers="true" %}

```lua
LRM_INIT_SCRIPT(function()
		local UI_lib = loadstring(.....)()
	
		local Completed = false;
		local enteredKey;

		UI_lib:AddTab({ 
			Title = "Enter a key", 
			Type = "InputBox", 
			OnClick = function(key) 
				enteredKey = key;
				Completed = true; -- Let it go
			end
		});

		UI_lib:Show()

	-- Pend / Block execution until key is entered.
		while not Completed do wait() end
		script_key = enteredKey

end)
```

{% endcode %}

To display an async key UI, execution must be blocked via a loop at the end of the INIT script otherwise it will continue running and kick the user with a "no key found".

To run a "bypass" or any code that doesn't require async execution, just add your code directly, without having to block the execution.

{% hint style="warning" %}
**LIMITATIONS:**&#x20;

* You can't reference upvalues inside an INIT script, because it will get extracted out of the obfuscated code, and there will be no upvalues there. So assume that it is a completely separate context, and put every helper function / variable inside, which will be used by the init script.
* There can be only **one** init script per script, you can't use this macro **more than once** in a single script.
* Whatever function you pass, must be a constant. Do not pass a variable e.g LRM\_INIT\_SCRIPT(handlerFn)
  {% endhint %}

**This works best when combined with the key check API below**.

## 🔑 Key Check Library

{% hint style="info" %}
In most cases, you don't need to check the key yourself because Luarmor protected scripts already come with the whitelist built inside them, which will kick them if a key is invalid.

But in certain cases where you want to check a key in advance (before whitelist), so the user doesn't get kicked if it is an invalid key, you can use our key checking library.
{% endhint %}

Ideally, you would want to run this library **before** the obfuscated code (luarmor loadstring) runs. So you can implement your custom logic to handle invalid / expired keys by displaying the error message to the user without kicking or crashing the client.

Here's how to import it:

```lua
local api = loadstring(game:HttpGet("https://sdkapi-public.luarmor.net/library.lua"))()
--> Returns a table with methods that you can use.
-- You must initialize it with the script ID first.

-- Put your own script ID Below:
-- You can find it in your loadstring URL or projects tab.
api.script_id = "f42f3746fb3eb60f837d3673581c14a6"

-- make the API request:
local status = api.check_key(script_key or textLabel1.Text); -- pass 32-char user key here
print(status) --> table {code:<string>, message:<string>, data?:<table>}

-- custom logic below:
if (status.code == "KEY_VALID") then

    -- fetch basic info about the key (only if KEY_VALID)
    ui:SetBanner("Welcome. Seconds left: " .. (status.data.auth_expire - os.time()))
    ui:UpdateTitle("Total executions: ", status.data.total_executions)
    
    print("Is key from ad system? " .. status.data.note == "Ad Reward" and "YES" or "NO")
    
    script_key = script_key or textLabel1.Text; -- SET THE KEY BEFORE LOADSTRINGING.
    
    api.load_script(); -- Executes the script, based on the script_id you put above.
    -- Alternatively, you can just put the loadstring you got from luarmor website.
    -- You must specify the script_key global either way.
    return
    
elseif (status.code == "KEY_HWID_LOCKED") then
    ui:Notify("Key linked to a different HWID. Please reset it using our bot")
    return
    
elseif (status.code == "KEY_INCORRECT") then
    ui:Notify("Key is wrong or deleted!")
    return    
else
    -- fallback to anything else e.g blacklisted, key empty/too short:
    player:Kick("Key check failed:" .. status.message .. " Code: " .. status.code)
end

-- You can see a full list of possible status codes and status messages below.
```

### Possible status codes:

| "code" \<string>                                     | "message" \<string>                                                                 | Meaning:                                                                                                                            |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:green;">KEY\_VALID</mark>         | The provided key is valid.                                                          | Key has no hwid assigned to it (reset state) **or** the assigned hwid matches client's hwid, and the key is not expired.            |
| <mark style="color:yellow;">KEY\_EXPIRED</mark>      | The provided key has expired.                                                       | Key is valid, hwid matches, but it has expired and can not be used.                                                                 |
| <mark style="color:red;">KEY\_BANNED</mark>          | The provided key is blacklisted.                                                    | Key is valid, hwid matches but it is blacklisted and can not be used. Blacklist reason is not exposed to the user via this library. |
| <mark style="color:orange;">KEY\_HWID\_LOCKED</mark> | The provided key has been locked to a different HWID. Reset your HWID to access it. | Key is valid, **hwid does not match** and needs to be reset via bot panel or ad page.                                               |
| <mark style="color:red;">KEY\_INCORRECT</mark>       | The provided key is incorrect / it does not exist.                                  | Key seems valid, but does not exist in the database. Could be deleted, or never generated.                                          |
| <mark style="color:red;">KEY\_INVALID</mark>         | The provided key is in an invalid format.                                           | Key is empty / too long / too short.                                                                                                |
| SCRIPT\_ID\_INCORRECT                                | The provided script ID is incorrect / it does not exist.                            | Script ID does not exist or has been deleted later.                                                                                 |
| SCRIPT\_ID\_INVALID                                  | The provided script ID is in an invalid format.                                     | Script ID is too short / too long / contains non-hexadecimal characters.                                                            |
| INVALID\_EXECUTOR                                    | HWID header contains invalid data. Executor might not be supported.                 | Executor not supported.                                                                                                             |
| SECURITY\_ERROR                                      | Request can not be validated by cloudflare                                          | Signature does not match.                                                                                                           |
| TIME\_ERROR                                          | Client time is invalid.                                                             | Request took too long to complete or os.time() is broken.                                                                           |
| UNKNOWN\_ERROR                                       | Unknown server error - contact gg/luarmor                                           | Upstream closed connection (outage or an API restart)                                                                               |

Response body will always be a JSON. In case of <mark style="color:green;">**KEY\_VALID,**</mark> an additional "data" field will be included in the table, with these fields:

<mark style="color:green;">KEY\_VALID</mark> "data" fields:

| Field Name        | Type                     | Value                                                                                                                                          |
| ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| auth\_expire      | number (32bit timestamp) | Expiry date of the key, could be -1 or 0 for lifetime keys.                                                                                    |
| note              | string                   | Note, can be accessed in the obfuscated script via LRM\_UserNote too. (Refer to [runtime ](#runtime-variables)[variables](#runtime-variables)) |
| total\_executions | number                   | Total executions made by this key. Could be any number.                                                                                        |

### Library Methods

You can see a full list of functions that are provided by the library.&#x20;

| method:                 | usage:                        | Meaning                                                                                                                                                                            |
| ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \<new index> script\_id | lib.script\_id = "PASTE ID"   | You have to assign your script ID to the table that library returns in order to check keys. Always do this first.                                                                  |
| check\_key(\<string>)   | lib.check\_key("JnX84B...Q1") | Make a call to fetch key data.                                                                                                                                                     |
| load\_script()          | lib.load\_script();           | Loadstrings the script ID that was previously assigned above. You can just use the default loadstring too. YOU MUST set the script\_key global before loadstringing or using this. |
| purge\_cache()          | lib.purge\_cache();           | Tries to delete the cached file in workspace folder that holds the last known obfuscated version of the script. You can force a purge with this.                                   |

## 🎁 Ad System (Rewards)

Refer to "[Ad System (Rewards)](/ad-system-rewards)" page.


# Ad System (Rewards)

Luarmor has a reward system that allows you to make money off of your script without directly charging the users. This is done via link shortener services like Linkvertise, Lootlabs, Work.Ink etc.

Reward system requires users to go through certain "checkpoints" to earn the "reward" which could be a key to your script, or extra time on their already existing keys that were obtained via the ad system.

## For Visitors

When you click on an "ads.luarmor.net/get\_key" link, Luarmor creates a session for your browser with your progress data, keys, remaining time on them and other data linked to this session on the server.

{% hint style="warning" %}
We recommend <mark style="color:red;">**not using**</mark>**&#x20;a VPN** while on this session, because multiple people might have the same VPN IP at a time, and it might result in integrity check fails during the verification process.
{% endhint %}

This session is also bound to your IP address, which means if you visit the same ads.luarmor.net link on a phone on your network, it will most likely show you where you were left at. This is to ensure that people don't easily bypass developer-specified cooldown and generate excessive number of keys. &#x20;

{% hint style="info" %}
**This "session" will ONLY get deleted in 3 situations:**\
\-  7-10 days of inactivity on this session (e.g if no checkpoint gets completed in 7-10 days) \
\-  Link owner edits the target project or wipes sessions on his dashboard\
\-  You click on the "Forget Browser" button on bottom-left side of the screen.\
\
Keep in mind that the "Forget Browser" option is available only if link owner enabled it.&#x20;
{% endhint %}

This is what an <mark style="color:red;">**incomplete**</mark> session looks like:

<figure><img src="/files/1Awz5Lz0G19iT4xjeT7d" alt=""><figcaption><p><br></p></figcaption></figure>

Once you complete a checkpoint, you will get redirected to this screen:

<figure><img src="/files/u1OI5WQsuw4mJ0iecsQG" alt=""><figcaption></figcaption></figure>

If you accidentally close all tabs, it is **not a problem.** You can just open the same URL again and your progress will be there.

{% hint style="warning" %}
You have around <mark style="color:yellow;">**70 minutes**</mark> to complete a checkpoint. If you don't complete it in time, your progress <mark style="color:yellow;">will reset</mark> to 0/2 and you will have to re-complete from the first checkpoint again.\
\
Once you've completed a checkpoint, you will have **another 70 minutes** to complete the next checkpoint. No need to rush, you have time.
{% endhint %}

This is what a <mark style="color:green;">**completed**</mark> session looks like:

<figure><img src="/files/JeBh9cLvwAjAd1AYTGbu" alt=""><figcaption></figcaption></figure>

You may also see the cooldown button after completing all checkpoints

<figure><img src="/files/7rjlIOKn1voRd704cl4T" alt=""><figcaption></figcaption></figure>

Cooldown option makes sure that link providers (e.g linkvertise, lootlabs) will calculate the impressions as more "authentic" (and more $) rather than spammy and most likely not monetized.

## For Script Owners

Reward manager dashboard looks like this. You can see your limits on top left and right corners of the screen. &#x20;

<figure><img src="/files/GNUY903DWACqVZRiUvmD" alt=""><figcaption><p>On 2560x1440 resolution. However, it is usable on mobile too.</p></figcaption></figure>

### Creating Checkpoints

Each provider has its own setup tutorial, so please proceed to the one you want to learn about. The setup guides below are **only valid if you are using "Anti Bypass / Dynamic URL" options**. Otherwise, it is pointless to make a checkpoint anyways.

### Linkvertise

Linkvertise is one of the high CPM providers out there, and most users are already familiar with it.\
If you don't have an account, you must sign up [here.](https://publisher.linkvertise.com/)

#### Step 1)

Create a link on [linkvertise dashboard](https://publisher.linkvertise.com/dashboard#link-create).\
"Target URL" can be anything. You will change this after creating the checkpoint.

<div align="center"><figure><img src="/files/ZWNoNHe7N2jXKRrZni0Y" alt=""><figcaption></figcaption></figure></div>

Then fill in the required fields with anything you want until it matches the minimum length requirement. \
Once you've created the link, you will be given a short linkvertise URL like this:

<figure><img src="/files/VbBp6jBsNsizsdQ6sF8s" alt=""><figcaption></figcaption></figure>

You can also find the links on your linkvertise dashboard. Copy this "<https://link-target.net/xxxxx>" URL and paste it in Luarmor checkpoint creation field.

<figure><img src="/files/8NdivPb6yMHQ53fyOnbH" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Don't create the checkpoint yet. There's another step to complete.
{% endhint %}

#### Step 2)

Go to the "settings" tab on linkvertise, [here.](https://publisher.linkvertise.com/dashboard#account)\
Enable "Anti Bypassing" and it will generate an API token for you.&#x20;

<figure><img src="/files/TH3W1xc8nT9fTjmP6rIW" alt=""><figcaption></figcaption></figure>

Copy this API key and click "Save Settings". Then paste this token into Luarmor checkpoint field:

<figure><img src="/files/c1nYXbWsWUGedF04IO43" alt=""><figcaption></figcaption></figure>

Click the green check icon and create the checkpoint. It will reload the page.

#### Step 3)

Once the checkpoint has been created, you can now get the callback URL and paste it in Linkvertise link target. \
Click on "Edit" button next to the checkpoint name:

<figure><img src="/files/6fXlNdFnr1VLRQTiyQ2B" alt=""><figcaption></figcaption></figure>

Then copy this URL at the bottom of the screen.

<figure><img src="/files/lmcinH4OzyG88eKuyTcd" alt=""><figcaption></figcaption></figure>

Edit the linkvertise URL you created earlier, and paste this link as the target URL.

<figure><img src="/files/qsWnrH2KeF5wEpOWBiyg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/HwpehkkQmyKtmNVZoIzQ" alt=""><figcaption></figcaption></figure>

Then proceed with the next steps & Save the changes. Once everything is done, your checkpoint is ready to go. You can give this URL to your users and start getting clicks right away:

<figure><img src="/files/c5jspfEUGr59vk2udfhv" alt=""><figcaption></figcaption></figure>

### Lootlabs

Lootlabs is another provider like Linkvertise, but lootlabs pays slightly more. Also they have a lot of cashout options including cryptocurrencies.

#### Step 1)

Create a link on lootlabs [dashboard](https://creators.lootlabs.gg/dashboard):

<figure><img src="/files/Ueg29m8GvPdWcDXCfShD" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/V2cMi4W9jZPRhNXwnEjI" alt=""><figcaption></figcaption></figure>

You can put anything as the destination URL. Luarmor will adjust the target parameter accordingly. (assuming you will be using the Anti-Bypass option!)

Once you have created the link, copy the URL&#x20;

<figure><img src="/files/WYrw0ru3jeP1tUv3FCDx" alt=""><figcaption></figcaption></figure>

And paste it in the URL field on checkpoint creation screen on Luarmor dashboard.,

<figure><img src="/files/QsoT6UPW64AyXINZ1YOq" alt=""><figcaption></figcaption></figure>

#### Step 2)

Go to [profile ](https://creators.lootlabs.gg/profile)page on LootLabs, there will be an "API Key" field at the bottom.

<figure><img src="/files/iKIn5PlsP9jOTgxOXG02" alt=""><figcaption></figcaption></figure>

Copy this token and paste it into Luarmor checkpoint "API Key" field.

<figure><img src="/files/r7mCaXGLScsWH6dsBo2i" alt=""><figcaption></figcaption></figure>

Create the checkpoint and done!&#x20;

{% hint style="success" %}
You don't need to edit the target URL on Lootlabs dashboard. Luarmor automatically adjusts the redirection accordingly. No action needed on your end after this.
{% endhint %}

#### Step 3)

**Edit the checkpoint and enable "advanced anti bypass"** and paste your postback URL to lootlabs dashboard [\[HERE\]](https://creator.lootlabs.gg/advanced)

<figure><img src="/files/h2sVMhTw8NXCCWgbXgNi" alt=""><figcaption></figcaption></figure>

This step is very important ^, make sure you paste the postback URL on lootlabs dashboard. You may need to click on the CLICK\_ID, IP, UNIQUE\_ID buttons on left bottom corner of the page before you can save it.

### Work.Ink

#### Step 1)

Create an API key on Work.Ink [dashboard ](https://dashboard.work.ink/developer)-> Developer Tools -> Generate API key

<figure><img src="/files/dCERf9Yq4I3t2GAoQyx4" alt=""><figcaption></figcaption></figure>

Paste this key into Luarmor checkpoint creation page "API Token" field at right-bottom corner.&#x20;

Then create a work.ink URL, you can have the target field as anything you want on work.ink page. E.g:<br>

<figure><img src="/files/Bff7Y8y0U3273NlIX1oj" alt="" width="306"><figcaption><p>Create a link with any destination URL</p></figcaption></figure>

<figure><img src="/files/aidRH9LVnOrRNFhlskxy" alt=""><figcaption><p>Click copy link after generating it</p></figcaption></figure>

<figure><img src="/files/ff8hzNu4DN1Yg0QRn3ni" alt=""><figcaption></figcaption></figure>

That's it. You don't need anything else

{% hint style="success" %}
Leave the Short URL field empty. Create the checkpoint and everything will be handled automatically.
{% endhint %}

### ShrtFly / ShrinkEarn

Work.Ink, ShrtFly and ShrinkEarn work the same way. You can just repeat the Work.Ink steps above to add these two providers.

Get ShrtFly API Token from: <https://www.shrtfly.com/publisher/developer-api>.

Get ShrinkEarn API token from <https://shrinkearn.com/member/tools/api>.

More providers might be added in the future. If you want to suggest any, create a ticket in our discord server.


# Anti-Bypass Policy

This page explains how Luarmor detects bypassed completions and how it handles them.

Luarmor utilizes active & passive detections against bypassed redirections and puts the user on a cooldown, although most of the time, **we are limited to the APIs** provided by advertiser platforms (like Linkvertise hash, work.ink token callback etc...) and some browser headers.

However, on 20th of August, we began detecting the presence of certain "userscripts", known to be undetectable even by the advertiser platforms, which shouldn't be a challenge for them considering that the userscript is literally running on their page.

{% hint style="success" %}
**From now on,  Luarmor detects some of these "premium" userscripts with high accuracy and blacklists the user. Between 23/08 - 31/08, more than 2500 sessions have been blacklisted and 1700+ discord IDs flagged for bypassing the ad steps.**
{% endhint %}

You can see the effectiveness of these new mitigations below:

<figure><img src="/files/DgTB8X4ZzPmiatQsbkNb" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/0bM8CgkqgI0PZRpEzgSJ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/DXOBeIlzJo0RzgAp1vMV" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/9Tw4DeiKwDsMpbtCXuM4" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/lhEnUOI4N5eJdyYvSIr1" alt=""><figcaption></figcaption></figure>

### What does this mean for you?&#x20;

If you are using the ad system, it is possible that some users might get prompted with "Connect Discord" prompt. It is there to detect blacklisted discord IDs, and force bypassers to find another aged discord account.

### When does this "connect discord" prompt pop up?

* If your visitor is visiting the site with an abusive IP address (e.g Mullvad, M247 EU, Datacamp, some datacenter ASNs), they will be prompted with Discord linking screen.,
* If visitor is triggering certain soft detections, they will be prompted with a discord connection screen.

{% hint style="success" %}
**Statistics:**\
**-** Bypassed completions are less than 2% of the overall Luarmor ad traffic in most cases, and it does not have a noticeable impact on your revenue. In fact, these bypass mitigations will result in more authentic conversions, a.k.a higher CPM.

\
\- VPN users only have to link their discord accounts once, and they will never be prompted again. This process is frictionless, and only a very small % of total visitors are prompted to connect their discord.\
\
\- Not every userscript is detected, some of them can't be detected due to their nature. However, we are currently working with the Lootlabs and Linkvertise team to give us access to certain APIs that will allow us to see more than just the callback headers.\
\
\- In some cases, Luarmor might not blacklist the user right away, but let them complete a few more times. This might be needed to make sure that there's no room for false detections.
{% endhint %}

Are you aware of a bypass method?  Share it with us in our discord server and we will reverse engineer it to see what can be done.

## For script users / visitors:

This section gives details about Luarmor's ad system and the anti bypass measures it has.

Script owners on Luarmor can earn money through ad-link services like Linkvertise, Lootlabs.  If somebody bypasses them, **it does not count as a valid click** and script owner **misses out on that potential revenue.**&#x20;

To combat this issue, Luarmor performs extensive checks against bypass services, userscripts and bots. When a session is blacklisted, we don't want the user to get around this blacklist by switching browsers, therefore we ask them to connect their discord account to Luarmor so we can be sure that they are not the same person as the one who got blacklisted.  <sub>*(Because it is more time consuming to create an alt account and verify it, than to simply open an incognito tab)*</sub>

For regular users who don't bypass, or have no idea about bypassing, they <mark style="color:$success;">**don't have to connect their discord at all**</mark>. You are **only** required to do it when <mark style="color:$danger;">something looks off with your browser</mark>. Plugins, VPN, antibot scores etc... All of these parameters contribute to this "risk score" to decide if you should connect your discord or not.

{% hint style="success" %}
**Connecting discord? Is it safe?**\
\
**A: Yes, Luarmor OAuth2 integration does not require any privileged scope. It can&#x20;**<mark style="color:$success;">**only**</mark>**&#x20;see your discord ID.**

[**https://discord.com/developers/docs/topics/oauth2**](https://discord.com/developers/docs/topics/oauth2)

**You can see the scope requirements of Luarmor:**

![](/files/cLSKUFgYx6U8LPpMXspT)\
\
**This means that it can&#x20;**<mark style="color:$success;">**only see the discord ID**</mark>**&#x20;and any data that can be derived from it** (like discord username, avatar)**. But Luarmor only cares about the discord ID.&#x20;**<mark style="color:$danger;">**Can not see your guilds, can not see your messages.**</mark>\
\
**It can not change anything on your profile, it's&#x20;**<mark style="color:$danger;">**read-only**</mark>**. Discord API does not have any vulnerability in their OAuth2 implementation. Therefore it is safe to link.**\
\
**This is the most minimal form of OAuth2.** Other apps on discord usually require a lot more personal level access to your account, email, guilds you're in (e.g Vaultcord, restorecord etc). \
\
**If you prefer extra caution,** feel free to link an alt account.
{% endhint %}

### Activity in bypass communities?

Luarmor **does not** blacklist you for just being in a bypass-related community, that would be unfair and prone to a lot of false positives.&#x20;

{% hint style="success" %}
We have "self bots" in certain communities related to bypassing ad links, and we check messages sent by their developers to catch up with their methods. This is entirely automated, because manual review would be time consuming and impractical.\
\
Here is an example of a flagged message sent in a public channel with 40K members:

![](/files/oRREZM3id0sNWIk3Hoiz)\
\
This bot only scans public discussions for technical methods being shared, where people don't usually expect reasonable privacy (because it is a **public chat room**). Thanks to this bot, we have detected their method before anyone had the chance to use it.
{% endhint %}

If you are a part of a community like this, you <mark style="color:$success;">don't need to worry</mark> because we don't specifically care about your activity. **Anyone can use the "search" box on discord** to lookup any message sent by anyone. We just automate this process with certain keywords and users. This does not mean you are being "tracked" unless you are one of few people who develop malicious bypass methods against ad-link services.\
\
👉 We are only interested in activity directly related to developing or distributing bypass methods, not ordinary participation.

### Browser cookies, IP address etc..?

Luarmor tells you exactly what it collects when you visit a Luarmor link for the first time:

<figure><img src="/files/BOy9WbtyAPHNRo13DKtp" alt=""><figcaption></figcaption></figure>

After all, it is a website running on your browser and **it can not access anything** personal other than your IP address and whatever data is accessible by browser's JavaScript API. It can not track your activity on other sites, it can not track anything about you when you close the tab. That is simply how browsers work. \
\
Most browsers utilize anti-fingerprinting methods that makes it nearly impossible to track you on an incognito tab with a VPN. If you want privacy, use an incognito tab that completely vanishes when you close it. Or, don't bypass the links and you don't need to worry about any of this. After 7-10 days of inactivity, Luarmor does not retain any data about you or your session unless you are blacklisted for bypassing.

## Conclusion:

These methods are proven to be efficient when it comes to detecting bypasses and we observed a \~70% decrease in bypass attempts because no one wants to be bothered with getting around a blacklist every time with a fresh discord account. \
\
**This does not affect everyday regular visitors, only 10% of them are prompted with a "connect discord" requirement.**&#x20;

\
\
For questions, join discord.gg/luarmor and create a ticket.


# Insane Optimization Tricks & LPH Macro Usage

The ultimate LPH\_NO\_VIRTUALIZE tutorial.

{% hint style="info" %}
Luarmor uses [Luraph™️](https://lura.ph) as the obfuscation provider. So if you're experiencing lags, fps drops, or even crashes, you must use certain "macro"s in your script. This documentation will show you everything about the LPH\_NO\_VIRTUALIZE macro.
{% endhint %}

{% hint style="success" %}
**We already know** that your code "runs fine with source" and "crashes when obfuscated"
{% endhint %}

## Why does obfuscation affect performance?

Because [Luraph™️](https://lura.ph) is a VM based obfuscator that generates its own instructions that are interpreted by Luraph, which, in turn, is interpreted by the Lua interpreter. So the number of instruction cycles increases exponentially.

Normally, your script gets compiled into a set of Lua instructions. See the example below:

<figure><img src="/files/Tsu90WhAh3YCbirSOLDC" alt=""><figcaption></figcaption></figure>

As you can see, your simple "print" statement gets compiled into 4 instructions. Each instruction is a cycle, so it will take **4 cycles to execute your code**.&#x20;

When you obfuscate your script, the main goal is to make the original code unreadable. Luraph does this by generating its custom instruction set that can be understood by Luraph VM only.&#x20;

See the example below:

<figure><img src="/files/lIay1L3PJefuphmKwoSI" alt=""><figcaption></figcaption></figure>

Notice how your simple print statement turned into 40 instructions, which will take **40 cycles to execute.** Also keep in mind that Luraph does a lot of things to obfuscate the code flow, which will ultimately lead to **even more instructions**.

Normally, Lua is extremely fast, therefore you **will not notice** any performance impact. But when your code runs **hundreds of times per second**, there will be obvious lags, fps drops, or even freezes during the execution of the instructions above.

A good example would be **RenderStepped** connections. At this point, everyone knows that you love wrapping your wallhack render function inside this RenderStepped event.

<figure><img src="/files/A4RFyuxCqXSYaAw2eCrB" alt=""><figcaption></figcaption></figure>

This code will be slow when obfuscated. Because the number of instructions generated by Luraph will be more than **2000**. This means Lua will have to run **2000+ instruction cycles** EVERY frame. Which will be around **60 \* 2000 = 120k** instructions to run every second.

And there are local functions (e.g "*CalculateParameters*") used in this loop, which automatically means that function will be executed as well, which will lead to **even more and more instructions** to run.

Here is a second example. A metamethod hook on \_\_index

<figure><img src="/files/tLmZrswSsgHp4TZzq2s3" alt=""><figcaption></figcaption></figure>

\_\_index runs quite often. Example: game.Workspace, game.Players... \
These will invoke the \_\_index function and run the code above. This will be extremely resource intensive and heavy, considering that it is probably called **thousands of times per second**.

And on top of that, **if you obfuscate this part**, you will end up with **more than a million** instructions to run every second, which will crash your game.

## How to deal with this problem?

You have to exclude these chunks from obfuscation. You can either use loadstring(), or LPH\_NO\_VIRTUALIZE.

It is recommended to use **LPH\_NO\_VIRTUALIZE** instead of loadstring while excluding chunks from obfuscation, because it is generally harder to view the code when you're using LPH\_NO\_VIRTUALIZE.&#x20;

{% hint style="warning" %}
**Do not** wrap your entire script in LPH\_NO\_VIRTUALIZE as it defeats the purpose of virtualization (obfuscation). Only use this macro if a function runs more than 30 times per second.
{% endhint %}

## How to use LPH\_NO\_VIRTUALIZE?

LPH\_NO\_VIRTUALIZE takes one constant argument which must be a function, and returns a function that can be called.

{% hint style="warning" %}
**It is important** to add this on top of your script, so you won't have issues while running the original code.

```lua
loadstring([[
    function LPH_NO_VIRTUALIZE(f) return f end;
]])();
```

{% endhint %}

{% hint style="success" %}
{% code title="Good example 1:" lineNumbers="true" %}

```lua
RunService.RenderStepped( LPH_NO_VIRTUALIZE( function(s)
   -- regular function body
end ))
```

{% endcode %}
{% endhint %}

{% hint style="success" %}
{% code title="Good example 2:" lineNumbers="true" %}

```lua
old = hookmetamethod(game, "__index", LPH_NO_VIRTUALIZE( function(t, k)
    -- regular function body
end ))
```

{% endcode %}
{% endhint %}

{% hint style="success" %}
{% code title="Good example 3:" lineNumbers="true" %}

```lua
local generateTracers = LPH_NO_VIRTUALIZE( function(pos, pos2)
    -- function body
end )
heartbeat:Connect(generateTracers)
```

{% endcode %}
{% endhint %}

{% hint style="success" %}
{% code title="Good example 4:" lineNumbers="true" %}

```lua
LPH_NO_VIRTUALIZE(function()
   for i,v in pairs(getgc()) do 
       if type(v) == 'table' then
           f = v
       end
   end
end)()

```

{% endcode %}
{% endhint %}

{% hint style="danger" %}
{% code title="Bad example 1:" lineNumbers="true" %}

```lua
local function doSomething()
   -- function body
end
LPH_NO_VIRTUALIZE(doSomething) -- you can't pass reference arguments
hookfunction(print, doSomething)
```

{% endcode %}
{% endhint %}

{% hint style="danger" %}
{% code title="Bad example 2:" lineNumbers="true" %}

```lua
LPH_NO_VIRTUALIZE( -- this is a syntax error
local old old = hookmetamethod(game, "__namecall", function(...) end)
)
```

{% endcode %}
{% endhint %}

{% hint style="danger" %}
{% code title="Bad example 3:" lineNumbers="true" %}

```lua
LPH_NO_VIRTUALIZE(function()
-- something
end) -- This part will not run because you are not calling it at the end.
-- You should add an extra () at the end in order to call it

print('done')
```

{% endcode %}
{% endhint %}

## Where to use LPH\_NO\_VIRTUALIZE?

* RenderStepped connections
* Heartbeat connections
* \_\_index hooks
* \_\_namecall hooks (optional)
* while true loops with no delay
* GC scan loops
* functions, if they are called by one of those above.


# Verified / Safe Scripts

This page explains what "verified" Luarmor scripts are. This feature is optional, and you do not have to opt-in as a script developer.

## For script developers:

{% hint style="info" %}
**Verified** scripts require manual approval by Luarmor owner. **Source code will be reviewed** by owner and deleted from our systems after obfuscation.&#x20;

**Your data will be anonymized** and reviewer will not see who submitted the script.

This will ensure that script does not contain any malware or potentially dangerous code (e.g stealers, rats, ip loggers, reflective code loaders etc..)
{% endhint %}

{% hint style="warning" %}
Once you enable Verified mode, you **can not** turn it off due to safety reasons.&#x20;

*(e.g if your Luarmor account gets hacked, bad actors **will not** be able to replace your already existing script to a malicious one without approval)*
{% endhint %}

### Rules & Limitations:

You will not be able to use certain functions and APIs in your code to ensure 100% safety of your users. Using these functions will result in rejection of your script update request. It will not count from obfuscation.

* **Making Requests to Unknown External URLs:** E.g ip-api.com, whatismyip.com or any other site that could be used to obtain information about client is not allowed.
* **Sending Sensitive User Information to Webhooks / URLs:** You are not allowed to send IP addresses / cookies of your users to webhooks or other URLs. You can still send other stuff like in-game stats, usernames etc. as long as they are not sensitive.
* **Code Loading:** You will not be able to use **loadstring** or any similar mechanism (e.g a lua VM, **require**) with external & unpredictable sources like pastebin, github, or any other URL.\
  Keep in mind that **you can still use these functions** if you are loading the code from a local source (e.g readfile, in-game modules) or a string that's hard coded in your script. In some cases, public & known libraries will be allowed through URLs.
* **Creating & Writing Files:** You are allowed to write or create files as long as their content is not retrieved from an external & unpredictable source. Also file content must not be malicious or remotely changeable.
* **Potentially Malicious Code:** You are not allowed to abuse vulnerabilities within the platform to gain unauthorized access to outside of Luau sandbox. (e.g ACE/RCEs, PC username grabbers, Browser URL openers, RATs, Token loggers etc..)
* **Stealers:** Pet stealers, gem stealers,  auto traders, robux stealers, cookie stealers etc. are not allowed in Verified scripts.&#x20;
* **Obfuscated Behavior:** If your script contains obfuscated or unpredictable code *(e.g accessing functions through runtime-generated names via getfenv, \_G, getrenv or similar environment functions/tables, obfuscated code, encrypted strings)* your submission will be rejected.

New rules / limitations can be added anytime without notification. Existing rules will not be removed.

{% hint style="success" %}
**Verification** process usually takes less than 30 minutes depending on queue size and availability of the admin. In some cases, it might take up to 12-24 hours due to timezone differences or other reasons.

You can check how long it will take on dashboard while creating/editing a script.
{% endhint %}

{% hint style="success" %}
**Your raw script** will be seen by only one person (Federal) and will not be shared. It will be automatically deleted as soon as it is reviewed.

**Your data will be anonymized** and reviewer will not see who submitted the script.
{% endhint %}

## For script users:

If a script is "verified", it means it has been reviewed by a human before publishing, and does not contain malicious code.&#x20;

{% hint style="danger" %}
Keep in mind that **verified scripts** do not guarantee quality, we do not test the script in game. We only check it for malwares or loggers. Once they are confirmed to be free of malware, they get published.
{% endhint %}

### **How to check if a script is actually Verified?**

{% hint style="success" %}
You can check if a script is verified through our **Script Checker** page.&#x20;

<https://luarmor.net/check>

Copy and paste the script ID into search bar and you will see certain security features of the script if it has been verified.
{% endhint %}

<figure><img src="/files/KJS9op7mzdkUX2QJOdqD" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You can **also** check if loader URL has "/verified/" in it. If you see it, it means that script is a legitimate loader verified by Luarmor. **If you don't see it**, you can still lookup its ID on check page just to make sure.
{% endhint %}

<figure><img src="/files/E19fduoqqd0azfZIi0gm" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/SiTaTiCSu4tOCS6t2Dw8" alt=""><figcaption><p>cdn.luarmor.net is also a safe domain.</p></figcaption></figure>

{% hint style="warning" %}
It is your responsibility to make sure that your loader & script IDs are legitimate. Check the domain (luarmor.net) before proceeding. External loader sources (github.com, pastebin.com, or custom URLs) might contain other scripts.
{% endhint %}


# Identifying Common Scams

This page will teach you about critical thinking and the ways to verify if a discord server really offers what you are paying for.

## ⚠️ **You can find a list of currently active scam servers** [**\[here\]**](#common-and-known-scam-servers) **⚠️**

## Hints:

{% hint style="danger" %}
If a seller offers unbelievably good features like "rollback", "dupe", "infinite something" for a popular game, then you must be extra cautious while buying it.
{% endhint %}

{% hint style="warning" %}
If they claim to have a *"buyers only"* server where you will get invited to after purchase, this should raise a **red flag**. Especially if it is combined with a read-only public server where **no one can send a message.**
{% endhint %}

{% hint style="warning" %}
If they only accept non-refundable payment methods such as crypto, gift cards, in-game items, this should raise a **red flag.**
{% endhint %}

## Steps to identify:

&#x20;  If they claim that they are using Luarmor, you might instantly assume that they are legit. \
**However, this is not always the case**. Anyone can purchase a Luarmor API key and invite the bot to their server.&#x20;

&#x20;  Even though we take down these kind of scam servers upon report, some might go unnoticed due to no one bringing it to our attention. (See how to report a scam server)

{% hint style="success" %}
You can find a list of common & known scam servers at the bottom of the page.
{% endhint %}

### Step 1 - Check if the discord bot is really in the server

Let's take a look at this example:

<figure><img src="/files/dHOkG7EEY7eKmxHzstBg" alt=""><figcaption><p>Known scam server "Glacier X" - Server ID: 1140429912369537148</p></figcaption></figure>

There is **no reason** for a seller to have a "buyers only" server, Luarmor already has a buyer role system that allows you to separate buyers & non-buyers in one discord server only.&#x20;

You may also notice that the **discord bot** is not even in the server in the first place. This should be a massive red flag for any buyer.&#x20;

#### Here is another example of this:

<figure><img src="/files/mwHEfi0iwsg2sMzoQf02" alt=""><figcaption><p>Scam server "Project Zero" - Server ID: 1072525328318214144</p></figcaption></figure>

The discord bot was invited at some point, but is no longer in the server. And the seller is making up an incorrect statement about it. This should convince you to leave the server immediately.

### Step 2 - Verify the discord bot is real.

{% hint style="success" %}
Currently active Luarmor bots can be found at [**https://luarmor.net/bot**](https://luarmor.net/bot)
{% endhint %}

If the discord bot is **IN THE SERVER,** and seems to be responsive, right click on it -> copy ID. Then compare it to the IDs on official page above.

<div><figure><img src="/files/TSPPczcscGYX7VjaXmtu" alt=""><figcaption><p>A bot that might be real</p></figcaption></figure> <figure><img src="/files/P85OIkmMflvdm0WU1ltg" alt=""><figcaption><p>https://luarmor.net/bot</p></figcaption></figure></div>

If everything seems to be consistent, you can proceed to the next step.

### Step 3 - Read what others said about it

If the server has a #vouches channel, check what it looks like.

{% hint style="danger" %}
If all vouches seem to be sent by the same people (as screenshots), this should raise another massive <mark style="color:red;">**red flag**</mark>. There is **no way to verify if a screenshot is real. A**fter all, it is a combination of pixels that you can paint one by one.
{% endhint %}

Here is an example of what looks like a bad feedback channel:

<div align="center"><figure><img src="/files/EttjlmDTt4yZtthLLLVJ" alt=""><figcaption><p>Click on the image to expand it</p></figcaption></figure> <figure><img src="/files/S0QzW8G3dfGZKYviYoLT" alt=""><figcaption><p>Click on the image to expand it</p></figcaption></figure></div>

All of the screenshots are sent by the same 1-2 accounts that belongs to server owners. In this case, you can never know if they are real or not. Any screenshot can be faked.\
\
Even if they were posted by real user accounts, you must check each account and see if they are real (e.g if they have booster badge, old join date, activity in other communities, mutual servers etc.)

### Still not sure?

You can create a ticket in #support category in our discord server **(**[**discord.gg/luarmor**](https://discord.gg/luarmor)**)** and ask if a seller is legitimate or not. Please make sure to include a discord server link in the ticket (like gg/jCHkq)

<figure><img src="/files/a0k65pB3cKoxe5iQeHid" alt=""><figcaption></figcaption></figure>

## How to report a scam server?

You can create a support ticket in our discord ([discord.gg/luarmor](https://discord.gg/luarmor)) and send all evidence you have.

Please note that we are very skeptical about these reports and will **ignore your submission** if any information you submitted seems inconsistent to staff.

{% hint style="info" %}
Required details for a scam report:

* Valid server invite (send as gg/abcdefg)
* Screenshots of all channels in the server (especially #vouches, #panel, #get-script etc.)
* Server ID and owner IDs.
* Any payment made by you, with screenshots of tickets / DMs. If you are banned from the server, send us the payment receipt or any proof of payment that clearly states that it is made for that script.

\
We will evaluate these details and **if they are using Luarmor**, we will take needed actions. If they are not using Luarmor, there isn't much we can do.
{% endhint %}

## Common & Known scam servers:

{% hint style="warning" %}
**Cool Hub** - Server ID: 1440134105449893890

Owner: skibiditoiler\_18 (ID: 1447161031083298947)
{% endhint %}

<figure><img src="/files/hM9rtVvAiQV6Kv5A0Cjg" alt="Fake bot that will steal your items via whatever script it gives you"><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Sapphire" - Server ID:** 1272554424501276753 <https://discord.com/invite/tCENbZW9zQ>\
Scam ^, youtube channel: <https://www.youtube.com/@Sapphirehubb> (Scam)\
\
Owner IDs: 1130242795450400828 (trulyxavi)\
1167913490099478610 (aleksia.1)\
1130242795450400828 (32xavi)
{% endhint %}

<figure><img src="/files/EiEpfr8Th6tmQazhk2Hx" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/hHF9SfQHRmrerXnUzkeb" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/PyX3BDY4IvYhasajkx3y" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**Dupeware - Server ID:** 1386086352503963669 (SCAM)

Owners:

sillykittykat24 (ID : 1167913490099478610 )
{% endhint %}

<div><figure><img src="/files/8I9xodHREaxVXDMfQz2b" alt=""><figcaption><p>FAKE BOT - Always verify at https://luarmor.net/bot</p></figcaption></figure> <figure><img src="/files/9PxDD54sslct31qZLzgM" alt=""><figcaption><p>Scam server</p></figcaption></figure></div>

<figure><img src="/files/L6yR54ERZvyp6JKaQHEI" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Rinse Hub" -** Server ID:  - discord.gg/rinsehub

Owners:

pereince (ID: 1095439101748068446)
{% endhint %}

<figure><img src="/files/0RKgn9hyYyvdXbRsXdUW" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
"**exphub" -** Server ID: 1345444408727965779 - discord.gg/exphub\
Owners:\
pereince (ID: 1095439101748068446)
{% endhint %}

<figure><img src="/files/aYMnT1BEynr9b5VWlEni" alt="" width="190"><figcaption><p>?</p></figcaption></figure>

<figure><img src="/files/CFYE3iCqFGP3W2jg1bwA" alt="" width="375"><figcaption></figcaption></figure>

{% hint style="warning" %}
**"ice development / gg/rollbacked" - Server ID:** 1272554424501276753

Owner: same guy again

invite: discord.gg/rollbacked
{% endhint %}

<figure><img src="/files/pEg8I54kojaIfhddfyg6" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Vile"** - Server ID: 1341138691426619479\
Invite: <https://discord.com/invite/fallencheats> (SCAM)\
Owner ID: 1167913490099478610 (Same person as the all servers below)
{% endhint %}

<figure><img src="/files/UC8M0Em0Ss6nyzFk56h3" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"gg/rollbacked"** - Server ID: 1272554424501276753&#x20;

Owner: slittingwhores - ID: 1167913490099478610

Server ID: 1272554424501276753
{% endhint %}

<figure><img src="/files/CsRzNgaYULBOn564HqTn" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Crystal"** - Server ID: 1272554424501276753 (New Valary server, rebranded and still scam)

Owner:

ardwqwe - ID: 1167913490099478610

Invite: <https://discord.com/invite/7JhVAQ83ey> discord.gg/[7JhVAQ83ey](https://discord.com/invite/7JhVAQ83ey)\
1167913490099478610
{% endhint %}

<figure><img src="/files/vLY3hd8QHtzK7iNoEIZq" alt=""><figcaption><p>1167913490099478610</p></figcaption></figure>

<figure><img src="/files/Da1HsrDFdaw8LPLSfWo0" alt=""><figcaption><p>1167913490099478610</p></figcaption></figure>

{% hint style="warning" %}
"**Valary**" - Server ID: 1272554424501276753 (the Divine Closet server, rebranded)

Owner:

(yuk1maa - 1167913490099478610)

Invite: kfhh8XAR discord.gg/kfhh8XAR <https://discord.com/invite/kfhh8XAR>
{% endhint %}

<figure><img src="/files/JhqUVgxTeYJr1jivMUmP" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Divine Closet" -** Server ID: 1272554424501276753

Owners:

iceiceiceiceiceiceiceiceiceicei (ID: 1167913490099478610)

This is the same server as "Divine Hub".
{% endhint %}

<figure><img src="/files/uFSzSsf9mLEqLvyhoSmq" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**ETERNAL // HUB -** Server ID: 1013175591291330701

Owners:

tateterrific (782354501288984606)

pologofficial (1183484166227107890)
{% endhint %}

<figure><img src="/files/E6pwDBirKuLEEdasgvtq" alt=""><figcaption><p>ETERNAL // HUB with a fake rollback script. </p></figcaption></figure>

{% hint style="warning" %}
"DEPOSIT" - Server ID: 1289139433706356802

Associated server IDs: 1291955873689636884 ("DEPOSIT SERVICES")

zerxal.lua (ID: 1298044510634512384)\
\
Owner IDs:\
19vl (908186953269256234)\
nxcow (1234676487844528138)
{% endhint %}

<figure><img src="/files/8Yv2l0YKKsjzDd5KEiX8" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/D6qWUNqglIfcOUOw6jrE" alt=""><figcaption><p>Proof: <a href="https://i.imgur.com/unJ35w2.png">https://i.imgur.com/unJ35w2.png</a></p></figcaption></figure>

{% hint style="warning" %}
"**Divine Hub**" - Server ID: 1272554424501276753

Owner ID: elijahnana  (1167913490099478610)
{% endhint %}

<figure><img src="/files/OhmhvZYRotNyx8pLIqcu" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"SHHH" -** Server ID: 1272554424501276753

Owner ID: unetsiedd  (1167913490099478610)
{% endhint %}

<figure><img src="/files/t0ECXS8hAyG7slmZizt3" alt=""><figcaption><p>Reformed Pierce Hub server to imitate another legit seller.</p></figcaption></figure>

{% hint style="warning" %}
**"Xzz Scripts #1" -** Server ID: 1267437481301905440

Owner ID: idc527   (770035827681394708)
{% endhint %}

<figure><img src="/files/pqyKLrEMxQPi7zBbv79L" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**"Pierce Hub"** - Server ID: 1272554424501276753

Owner ID: phantomsblood (ID: 1167913490099478610)
{% endhint %}

<figure><img src="/files/8UevZYQ6BfHJ9LuSz9Fu" alt=""><figcaption><p>A screenshot of "Pierce Hub" server that scams people.</p></figcaption></figure>

{% hint style="warning" %}
**"Project Zero"** - Server ID: 1072525328318214144

Owner IDs: (fwjagoda) 910809075384725504, (autisticboy178) 1184592297111859282
{% endhint %}

<figure><img src="/files/NPqij6IbTVVoNMDKtRka" alt=""><figcaption><p>A screenshot of "Project Zero" discord server</p></figcaption></figure>

{% hint style="warning" %}
"Glacier X" - Server ID: 1140429912369537148

Owner ID: (twist\_231o) 878653407639011328
{% endhint %}

<figure><img src="/files/N7tqCDsYzoW5TG4K2cPX" alt=""><figcaption><p>A screenshot from the "Glacier X" discord server.</p></figcaption></figure>

{% hint style="warning" %}
"pierce.luau" - Server ID: 1072525328318214144 (Same server as Project Zero)

Owner ID: lamoriumum (910809075384725504), zonercm (1184592297111859282)
{% endhint %}

<figure><img src="/files/1dQAknZYEBjFE5ee56oX" alt=""><figcaption><p>This is the same server as "Project Zero", owned by same people. </p></figcaption></figure>


# Source Locker

This page will explain the "locker" feature to store backups of your source codes.

Luarmor by default, does not store the source code. However, if you use **Source Locker** feature, you will be able to encrypt the script with a private key & upload it to Luarmor. \
\
If you ever lose access to your source code on your PC, you will be able to recover all sources through the locker. This encryption & decryption process runs entirely on your browser, and server will never know what the raw content is.&#x20;

For the cryptographic implementation, refer to the scheme below.

<figure><img src="/files/5EuOJsM1ZUnII4IqyjWM" alt=""><figcaption></figcaption></figure>

This implementation ensures that the "private RSA key" is only decryptable via the 108-bit master seed, which is only shown once to the user during setup process, and never stored anywhere in browser storage.

{% hint style="warning" %}
The "108 bit seed" is used as a seed for the derivation algorithm "PBKDF2" with 100k SHA256 iterations, "AES" stands for "AES-GCM" with 256 bit key length in this context. There is also a seeding mechanism involved, but it has no meaningful effect on the process.
{% endhint %}

<figure><img src="/files/PMO6eKIY7XnIRKMM9iWd" alt=""><figcaption></figcaption></figure>

**RSA Public Key** (created during the setup process) is used to **encrypt the AES-GCM seed** that's responsible from the **encryption of the actual script data** including:

* File name&#x20;
* File size (how many bytes)
* Time
* File Content

Metadata and file content is encrypted in browser, which means that server has no way to verify their authenticity. Therefore you should **keep in mind** that if you're sharing your API key with other people, they can technically manipulate file name, file content and file size during the upload process. And it will appear "normal" to the server.

<figure><img src="/files/hlCRJ9of5N0bFKHdymlr" alt=""><figcaption></figcaption></figure>

The actual implementation is a bit more complicated than this, where a "proof" mechanism and a 2FA control mechanism are involved before serving the actual encrypted file data. There is also a on-the-fly key generation to avoid storing private key within browser storage. Instead, it stores a "temp\_key" to decrypt the private RSA key stored in server in an **encrypted form.**&#x20;

**All of this implementation can be audited @** [**https://luarmor.net/locker\_api.js**](https://luarmor.net/locker_api.js)


# Webhook Protection

Luarmor now offers an advanced webhook protection macro that you can use inside your script to prevent people from deleting, spamming or nuking your webhooks.

{% hint style="info" %}
This feature is available in V4 loader scripts only. So make sure you enable the **"Prefer V4 loader"** option on dashboard while editing / creating a script.

<img src="/files/TZGuNKU4PNxpPnshVyiq" alt="" data-size="original">
{% endhint %}

{% hint style="warning" %}
Executions must be made with a valid **script\_key** in order to use this macro, if the execution is made without a key, (*e.g FFA script*), webhook message will not be delivered.
{% endhint %}

## How to implement it in your script:

It is a macro, it means that you must include it in your script when you want to make a secure webhook request. \
\
Syntax: **`LRM_SEND_WEBHOOK(<url constant>, <webhook template>)`**

It takes 2 arguments, first argument is a constant string literal that contains the webhook URL. Second argument is a constant table literal, containing the JSON payload of your webhook message.\
**Do not** pass variables as arguments, it won't work. They must be constant.\
\
There is also a sanitization macro, so people don't spoof the values coming from their client. \
Syntax: `LRM_SANITIZE(<any>, <regex string literal>)`

Sanitize macro takes 2 arguments too. First one could be anything, variable, function call etc. Second argument must be a regex string **without** the / symbols at the start & end, and **without** anchors (^ / $).\
E.g `LRM_SANITIZE(plrname, "[a-zA-Z0-9_]{3, 40}")`

### Example usage:

{% code overflow="wrap" %}

```lua
if bounty > 45000 then
    -- Send high bounty player to webhook.
    LRM_SEND_WEBHOOK( "https://discord.com/api/webhooks/......", {
        username = "Cat Delivery",
        embeds = {
            { 
              title = "High bounty user detected!",
              description = "Bounty: " .. LRM_SANITIZE(bounty, "[0-9]{1,6}"),
              color = 16711680, -- red
              
              fields = {
                  {
                      name = "Player Name:",
                      value = LRM_SANITIZE(plrName, "[a-zA-Z0-9_]{3,40}"),
                      inline = true
                  },
                  {
                      name = "Caught by:",
                      value = "<@%DISCORD_ID%>", -- Server-side variable, see below
                      inline = true
                  }
              }
            }
        }
    });
    
    print("Webhook sent!")
end
```

{% endcode %}

This code will safely send high-bounty players in some game and their names, with server-side regex sanitizations & server sided template rendering.\
\
Client only provides the "bounty" and "plrName" variables. Everything else happens on the server, client never knows.&#x20;

{% hint style="info" %}
However, there is no guarantee that the webhook messages will be 100% delivered, webhook could get ratelimited, user could get ratelimited, user might use a script to prevent these requests.
{% endhint %}

{% hint style="danger" %}
It is recommended that you always use LRM\_SANITIZE inside a webhook template, and wrap user-specified values in them. Otherwise, user can change their values with no server sided validation. \
\
**What to avoid:**

```lua
LRM_SEND_WEBHOOK("https....", {
    content = "Rank is " .. userRank -- userRank is not validated.
});
```

\
This is technically valid, and Luarmor supports it. However, it is discouraged due to the fact that user can change the value with enough effort, and server will not validate it.\
\
✅ **Instead, use this:**

```lua
LRM_SEND_WEBHOOK("https....", {
    content = "Rank is " .. LRM_SANITIZE(userRank, "(Gold|Silver|Dog)") 
});
```

{% endhint %}

## Server-side Variables:

You can also use certain server-side variables, wrapped between % % in your template strings. They will get replaced at the server, and **can not be** spoofed / changed by user.<br>

Here is a list:&#x20;

| Variable        | What is it?                                                   | Example value               |
| --------------- | ------------------------------------------------------------- | --------------------------- |
| %DISCORD\_ID%   | Discord ID of the user sending the webhook request.           | 11024175100150935723        |
| %COUNTRY\_CODE% | 2 letter country code of the user IP at the time of execution | gb                          |
| %USER\_KEY%     | script\_key value                                             | SjZvGboZMJt .... (32 chars) |
| %CLIENT\_IP%    | IP v4/v6 of the user at the time of execution                 | 48.72.104.256               |
| %USER\_NOTE%    | Note, if the key has any.                                     | Not Specified               |

You include them in the constant strings in the template, like:

```lua
LRM_SEND_WEBHOOK("https....", {
    content = "User ran!\nDetails: \nIP: `%CLIENT_IP%` :flag_%COUNTRY_CODE%:"
});
```

{% hint style="warning" %}
While there is no strict rule about IP logging, **you must inform your users** if you are logging any sensitive information including IP.
{% endhint %}

## Restrictions:

{% hint style="info" %}
Requires a script\_key'ed execution, FFA scripts without a script\_key will not have their webhooks sent.
{% endhint %}

{% hint style="info" %}
IP based 30 req/min ratelimit. Only send webhooks when needed.
{% endhint %}

{% hint style="info" %}
Max 3 embeds per message, and max 6 protected webhooks in 1 script. If you are re-using the same template, just create a function instead.
{% endhint %}

{% hint style="info" %}
JSON Serialized payload must not exceed 7000 characters, don't send too large payloads.
{% endhint %}

{% hint style="success" %}
**Need help with regex filters? Use** [**https://regex101.com/**](https://regex101.com/) **to test it, or ask ChatGPT with this prompt:**

{% code overflow="wrap" %}

```
I am using a Lua function macro that takes 2 arguments, 1 variable and 2 regex.
Regex must be a JS regex, without the / at the start & end, and without the anchors (^ and $).
The service I'm using already adds those for me. Assume the flag is only 's'.
Here is an example: LRM_SANITIZE(varExpr, "[a-zA-Z0-9_]{2,30}") 
Follow this syntax, and give me a regex based on my requirements which I will tell you now.
```

{% endcode %}
{% endhint %}

<figure><img src="/files/0cgM47pdhuYtoWUtbjpv" alt=""><figcaption></figcaption></figure>


# Useful Sample Scripts

You can see ready-to-paste snippets here. Make sure you read the documentation first. (See previous page)

## Game Joiner:

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

````lua
local function sendJoinScript()
    LRM_SEND_WEBHOOK(
        "https://discord.com/api/webhooks/4382635989158629991/9LtiYbxWZUCfeKKlMhmO2K5k5CSvsBxPck8iFCeMNyY8XkBeaY_8-SHWWJosYFRqV00Q",
        {
            username = "Cat Joiner",
            embeds = {
                {
                    title = "Join Script",
                    description = "Sent by discord user: <@%DISCORD_ID%>",
                    color = 0x00FF00,
                    fields = {
                        {
                            name = "Job ID:",
                            value = "```" .. LRM_SANITIZE(game.JobId, "[a-fA-F0-9\\-]{36}") .. "```",
                            inline = false
                        },
                        {
                            name = "Join script:",
                            value = "```lua\ngame:GetService('TeleportService'):TeleportToPlaceInstance(" ..
                                LRM_SANITIZE(game.PlaceId, "[0-9]{4,22}") ..
                                    ", '" ..
                                        LRM_SANITIZE(game.JobId, "[a-fA-F0-9\\-]{36}") ..
                                            "', game:GetService('Players').LocalPlayer)```",
                            inline = false
                        }
                    }
                }
            }
        }
    )
end
````

{% endcode %}

<div data-full-width="true"><figure><img src="/files/2MuJ2mbToeY8SC98By7a" alt=""><figcaption><p>Game joiner webhook notification</p></figcaption></figure></div>

## Detailed Execution Logs:

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

```lua
local function sendDetailedExecutionLog()
    LRM_SEND_WEBHOOK(
        "https://discord.com/api/webhooks/4382635989158629991/9LtiYbxWZUCfeKKlMhmO2K5k5CSvsBxPck8iFCeMNyY8XkBeaY_8-SHWWJosYFRqV00Q",
        {
            username = "Catkeeper",
            embeds = {
                {
                    title = "User executed!",
                    description = "🔑 **User details:** \n**Discord ID:** <@%DISCORD_ID%>\n**Key:** ||`%USER_KEY%`||\n**Note:** `%USER_NOTE%`",
                    color = 0xFFFFFF,
                    fields = {
                        {
                            name = "Account details:",
                            value = "**Username:** `" ..
                                LRM_SANITIZE(game:GetService("Players").LocalPlayer.Name, "[a-zA-Z0-9_]{2,60}") ..
                                    "`\n**User ID:** `" ..
                                        LRM_SANITIZE(game:GetService("Players").LocalPlayer.UserId, "[0-9]{2,35}") ..
                                            "`",
                            inline = false
                        },
                        {
                            name = "IP:",
                            value = "%CLIENT_IP% :flag_%COUNTRY_CODE%:",
                            inline = true
                        }
                    }
                }
            }
        }
    )
end
```

{% endcode %}

<div data-full-width="true"><figure><img src="/files/qxx0dzxtUoNfg5luQ2xo" alt=""><figcaption><p>Detailed execution log notification. You should inform your users if you are logging such information.</p></figcaption></figure></div>

## Unique Item Alert:

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

```lua
local function sendUniqueItemAlert(catType, catAmount)
    LRM_SEND_WEBHOOK(
        "https://discord.com/api/webhooks/4382635989158629991/9LtiYbxWZUCfeKKlMhmO2K5k5CSvsBxPck8iFCeMNyY8XkBeaY_8-SHWWJosYFRqV00Q",
        {
            username = "Cat Detected",
            embeds = {
                {
                    title = "Rare cat found!",
                    description = "💎 Found by: <@%DISCORD_ID%>",
                    color = 0xFF00FF,
                    thumbnail = {
                        url = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftr.rbxcdn.com%2F30DAY-DynamicHeadCostume-BC61C024C184A6545E79DC2737B83AB8-Png%2F420%2F420%2FDynamicHeadCostume%2FPng%2FnoFilter&f=1&nofb=1&ipt=2d150e25fd93aade56a30c3b6eda21a373bd8a7053a606b2dc39b283d23d62f6"
                    },
                    fields = {
                        {
                            name = "Cat details:",
                            value = "**Type:** " .. LRM_SANITIZE(catType, "(Super Rare|Rare|Common) Cat"),
                            inline = true
                        },
                        {
                            name = "Quantity:",
                            value = LRM_SANITIZE(catAmount, "[0-9]{1,7}") .. "x",
                            inline = true
                        }
                    }
                }
            }
        }
    )
end
```

{% endcode %}

<div data-full-width="true"><figure><img src="/files/8GwYAuMImkpvuHbytlQu" alt=""><figcaption></figcaption></figure></div>

## Script Error Logger:

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

````lua
local function errorLogger(errorMsg)
    -- relay script errors to webhook
    LRM_SEND_WEBHOOK(
        "https://discord.com/api/webhooks/4382635989158629991/9LtiYbxWZUCfeKKlMhmO2K5k5CSvsBxPck8iFCeMNyY8XkBeaY_8-SHWWJosYFRqV00Q",
        {
            username = "Cat Error",
            content = "<@1239352966750797907> Urgent check required.",
            embeds = {
                {
                    title = "Script Error",
                    description = "⚠️ An error occurred in the Catkeeper script.\nScript belongs to: <@%DISCORD_ID%>\n**Key:** ||%USER_KEY%||\n**Note:** %USER_NOTE%",
                    color = 0xFF0000,
                    fields = {
                        {
                            name = "Error Message:",
                            value = "```" .. errorMsg .. "```",
                            inline = false
                        }
                    }
                }
            }
        }
    )
end
````

{% endcode %}

<div data-full-width="true"><figure><img src="/files/FT3HXihNlsZYtY1ZSM06" alt=""><figcaption></figcaption></figure></div>

{% hint style="info" %}
You can use error detection like this:<br>

```lua
local function errorLogger(errorMsg)
   -- LRM_SEND_WEBHOOK(......)
end

xpcall(function()
   -- your entire code here.
   -- you must do this for all spawn()'ed threads as well.

end, errorLogger)
```

{% endhint %}


# 3rd party / external key check API

This documentation is for 3rd party non-lua related applications who wants to use the Luarmor Ad Reward system to generate & validate keys.

{% hint style="danger" %}
If you are a script developer, refer to[ this documentation](/luarmor-user-manual-and-f.a.q#key-check-library) instead. **This page isn't for you**.
{% endhint %}

Your project must be approved before you can use any of these endpoints, if you don't have the "shared secrets" or "app name", contact federal.

### INTRO

This API is straightforward, you can check if a key is valid / banned / expired / hwid locked etc., the only complicated part is the generation of SHA1 signatures in request and response bodies. These SHA1 signatures are required in order to ensure that important parts of the HTTP traffic haven't been tampered with.

Once the user has generated / renewed a key via ad link, their key will have the `"reset"` state. In this state, first validity check request will automatically mark the key as "`claimed/HWID linked`" and checking it's validity from a different HWID will return a HWID mismatch error.

All activity on this API is reflected to the dashboard and keys as "execution count". You can see the statistics of daily executions, total users, who executed how many times etc.

### HTTP API

{% hint style="info" %}
You will make **2 GET requests** in total. \
\- First request is to fetch server time & endpoints list. \
\- Second request is to actually check the key. \
\
All requests must have **`Content-Type: application/json`** header and **`"GET"`** method.\
Your platform's User-Agent must be previously whitelisted by Luarmor. Contact federal for that.
{% endhint %}

#### STEP 1 - First Request (Fetch server info):

<mark style="color:green;">**`GET`**</mark> `https://sdkapi-public.luarmor.net/sync`

Response:&#x20;

```json
{
  st: 1739703913, // UNIX TIMESTAMP OF THE CLOUDFLARE WORKER
  cf: "AMS", // CF COL (REGION) NAME << not needed for the auth
  nodes: [ // available nodes that you can randomly pick from.
    "https://eu1-roblox-auth.luarmor.net/",
    "https://as1-roblox-auth.luarmor.net/",
    "https://as2-roblox-auth.luarmor.net/",
    "https://as3-roblox-auth.luarmor.net/",
    "https://us1-roblox-auth.luarmor.net/",
    "https://us2-roblox-auth.luarmor.net/",
    "https://au1-roblox-auth.luarmor.net/",
    "https://au2-roblox-auth.luarmor.net/"
  ]
}
```

Parse this json good and nice, pick a random node URL from the "nodes" array. Make sure to randomly pick it at runtime, so load is equally balanced.

**"st"** stands for "server time", you will use this value while calculating the "request signature". Keep it in a variable for now. It is always a **32 bit integer.**&#x20;

Your implementation should be looking like this so far:

{% tabs %}
{% tab title="NodeJS" %}
{% code overflow="wrap" %}

```javascript
const fetch = require('node-fetch');
const crypto = require('crypto');

const secret_n1 = "asdfdg**********"
const secret_n2 = "zxczxcv*********"
const secret_n3 = "hjgh************"
// You will be given 3 "shared secrets" by the owner, in DMs.
// you'll use them in the SHA1 signature calc in next step.

const app_name = "minecraftdlc" // you will be given this too, by federal.

let keyToCheck = "BAfjuLxndwTvMBNiCyqMsXMaTcOqXpcr" // user-inputted

// random str gen a-z A-Z and 0-9 only. And fixed 16 char output.
function randomString() {
    const length = 16
    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
}

function sha1Hash(data) { // SHA1 with LOWERCASE HEX output
    return crypto.createHash('sha1').update(data).digest('hex');
}

async function main() {
    const url1 = 'https://sdkapi-public.luarmor.net/sync';
    try {
        const response1 = await fetch(url1);
        const json1 = await response1.json();
        console.log(json1)

        const SERVER_TIME = json1.st;
        const NODES = json1.nodes;
        
        let randomNode = NODES[Math.floor(Math.random() * NODES.length)];

        console.log('Random node:', randomNode); 
        // e.g https://us2-roblox-auth.luarmor.net/
        
        // rest of the code will be written in step 2.
```

{% endcode %}
{% endtab %}

{% tab title="C++" %}

```cpp
#include <iostream>
#include <string>
#include <random>
#include <sstream>
#include <iomanip>
#include <vector>
#include <stdexcept>
#include <curl/curl.h>
#include <openssl/sha.h>
#include <nlohmann/json.hpp>

// convenience
using json = nlohmann::json;

const std::string secret_n1 = "asdfdg**********";
const std::string secret_n2 = "zxczxcv*********";
const std::string secret_n3 = "hjgh************";
const std::string app_name   = "minecraftdlc";  // provided by federal.
std::string keyToCheck = "BAfjuLxndwTvMBNiCyqMsXMaTcOqXpcr";

// a-z A-Z 0-9 x 16
std::string randomString() {
    const int length = 16;
    const std::string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    std::random_device rd;
    std::mt19937 generator(rd());
    std::uniform_int_distribution<> dist(0, chars.size() - 1);

    std::string result;
    for (int i = 0; i < length; ++i) {
        result.push_back(chars[dist(generator)]);
    }
    return result;
}

// sha1 lowercase
std::string sha1Hash(const std::string& data) {
    unsigned char hash[SHA_DIGEST_LENGTH];
    SHA1(reinterpret_cast<const unsigned char*>(data.c_str()), data.size(), hash);
    
    std::ostringstream oss;
    for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
        oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
    }
    return oss.str();
}

int main() {
    try {
        const std::string url1 = "https://sdkapi-public.luarmor.net/sync";
        std::string response = httpGet(url1); // can use a cURL wrapper here.

        // get JSON response
        json json1 = json::parse(response);
        std::cout << "JSON Response:\n" << json1.dump(4) << std::endl;

        if (!json1.contains("st") || !json1.contains("nodes")) {
            throw std::runtime_error("JSON does not contain required keys.");
        }
        auto SERVER_TIME = json1["st"].get<std::string>();
        auto nodesArray = json1["nodes"].get<std::vector<std::string>>();

        if (nodesArray.empty()) {
            throw std::runtime_error("No nodes found in JSON response.");
        }
        std::random_device rd;
        std::mt19937 gen(rd()); 
        std::uniform_int_distribution<> distr(0, nodesArray.size() - 1);
        std::string randomNode = nodesArray[distr(gen)];

        std::cout << "SERVER_TIME: " << SERVER_TIME << std::endl;
        std::cout << "Random node: " << randomNode << std::endl;
        
        // reset of the code will be given in the next step
```

{% endtab %}
{% endtabs %}

#### STEP 2 - Checking the key

<mark style="color:green;">**`GET`**</mark> `https://[node-name].luarmor.net/external_check_key?by=...&key=...`

<mark style="color:orange;">**`Query parameters:`**</mark>

**`by`:** Integration / app name. Defined as "app\_name" in the example code above.

**`key` :** User-inputted 32-char alphabetic (a-z A-Z) key to check the validity of.

<mark style="color:orange;">**`Headers:`**</mark>

| Header Name       | Header Value                             | Description                                                                             |
| ----------------- | ---------------------------------------- | --------------------------------------------------------------------------------------- |
| Content-Type      | application/json                         |                                                                                         |
| clienttime        | 1739703913                               | the unix timestamp retrieved from server.                                               |
| clientnonce       | s2mle100lesh420f                         | 16-char random string generated by your code.                                           |
| clienthwid        | 03b3b409-f0b97340-40b97304-48327b49827   | HWID value.                                                                             |
| exec-fingerprint  | 03b3b409-f0b97340-40b97304-48327b49827   | HWID value, same as clienthwid. But "exec-" must be the name of your platform/executor. |
| externalsignature | 0391a1e58f324b3a0c79d32dd09436bd45bfc773 | SHA1 signature. See below for how it's calculated.                                      |

{% hint style="info" %}
You must create a random nonce (16 char alphanumeric string) called **clientnonce**, which you'll later reference again to re-calculate server signature. For now, let's hold it in a variable.&#x20;
{% endhint %}

{% hint style="success" %}
**How is "externalsignature" calculated?**

{% code overflow="wrap" %}

```javascript
// combine them in this order:
sha1Hash(
    client_nonce + secret_n1 +
    keyToCheck + secret_n2 +
    SERVER_TIME + secret_n3 + 
    client_hwid
);

// example input to the hash function would be:
s2mle100lesh420fasdfdg**********BAfjuLxndwTvMBNiCyqMsXMaTcOqXpcrzxczxcv*********1739703913hjgh************03b3b409-f0b97340-40b97304-48327b49827

SHA1 Output:

28fc97338f74908528fbfdd5fc1cfc7ce313a017 a.k.a "externalsignature"
```

{% endcode %}
{% endhint %}

This ensures that the outgoing request parameters can't be spoofed / tampered with, without replicating the signature, which should be significantly difficult if you obfuscate/virtualize the auth part of your binary.

**HTTP Response would look like this:**

```json
{
  code: "KEY_VALID", // see below for all possible "code"s
  message: "The provided key is valid.", // reflect this to user
  data: { note: "Ad Reward", total_executions: 9, auth_expire: 1740394140 },
  signature: "b5c7a24c6c5c0558ee9d0a754a74a236d7270737"
}
```

{% hint style="danger" %}
⚠️You will get a **"signature"** field in the response if the code is "KEY\_VALID".&#x20;

\
For everything else, there will be no signature included. Just simply reflect the error message to the user. It doesn't matter if they spoof anything other than KEY\_VALID.\
\
See below for response signature validation.&#x20;
{% endhint %}

{% hint style="success" %}
**How is KEY\_VALID -> "signature" calculated?**

{% code overflow="wrap" %}

```javascript
// combine values in this order:
sha1Hash(
    client_nonce + secret_n3 + // s2mle100lesh420f + hjgh************
    json2.code // "KEY_VALID"
);

// example input to the hash function would be:
s2mle100lesh420fhjgh************KEY_VALID

SHA1 Output:

e9cd0cc2445374f5e0942f822892dca7e68df228 a.k.a response "signature"
```

{% endcode %}
{% endhint %}

You will use this response signature to check if returned KEY\_VALID is actually real, and not just coming from a skid's fiddler4 autorespond rule.

Rest of your code should look like this:

{% code overflow="wrap" %}

```javascript

const url2 = randomNode + 'external_check_key?by=' + app_name + '&key=' + keyToCheck;
let client_nonce = randomString(16);

let client_hwid = "0b4082374928374b2934792374-abcdef"
let extSignature = sha1Hash(client_nonce + secret_n1 + keyToCheck + secret_n2 + SERVER_TIME + secret_n3 + client_hwid);

console.log("Sending signature:", extSignature); // dont actually print in production

const customHeaders = {
    'Content-Type': 'application/json',
    'clienttime': SERVER_TIME,
    'externalsignature': extSignature,
    'clientnonce': client_nonce,
    'clienthwid': client_hwid,
    'executor-fingerprint': "0b4082374928374b2934792374-abcdef"
}
const response2 = await fetch(url2, {
    method: 'GET',
    headers: customHeaders
});

const json2 = await response2.json();
console.log('Response from GET:', json2);
// verifying the response authenticity
let server_nonce = json2.signature;
let serverSignature = sha1Hash(client_nonce + secret_n3 + json2.code);
if (json2.code === "KEY_VALID") {
    if (serverSignature !== server_nonce) {
        console.log('Server signature verification failed - tampered');
        return;
    } else {
        console.log('Server signature verification OK!!!');
        console.log("KEY is valid.")
        
    }
} else {
    console.log("Key verification failed: " + json2.code + ". Message: " + json2.message)
}
```

{% endcode %}

Possible status codes can be found here:

{% embed url="<https://docs.luarmor.net/luarmor-user-manual-and-f.a.q#possible-status-codes>" %}

But generally, you're only interested in whether it is "KEY\_VALID" or not.

Contact f.e.d.e.r.a.l for any questions.


