> For the complete documentation index, see [llms.txt](https://merithic.gitbook.io/merithic-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://merithic.gitbook.io/merithic-docs/api-reference/integrating.md).

# Integrating step by step

The endpoint pages describe each call on its own. This page is the whole job in order: what to ship inside your model, what runs at server start, and what to do when something fails.

## What you're building

```
Buyer's game starts
  │
  ├─ 1. /check      Is this key valid, for this user, for this product?
  │                 └─ no  → refuse to run
  │
  ├─ 2. /unlock     Fetch the real payload (your actual code/config)
  │                 └─ sealed with the buyer's key — a leaked copy has nothing
  │
  └─ 3. /version    Is there a newer build than the one I'm holding?
          └─ yes → /update, verify, rebuild the tree
```

Steps 1 and 3 are cheap. Step 2 is what makes a stolen copy useless.

## Before you write any code

You need three things in the product you ship:

|                           | Where it comes from                                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Your **product ID**       | The product's page in your cockpit.                                                                              |
| A **license placeholder** | A distinctive token you type into your script, e.g. `__UPDATR_LICENSE__`. Set it on the product's **Files** tab. |
| **HTTP requests enabled** | Game Settings → Security → Allow HTTP Requests.                                                                  |

On download, Updatr replaces the placeholder with that buyer's key. Every copy is therefore unique, which is what lets you tie a leak back to an account.

{% hint style="warning" %}
The placeholder must appear **in the script's source you uploaded**. If you change the token later, re-upload; the injector matches the exact string.
{% endhint %}

## Step 1 — Refuse to run without a valid license

`/check` is a plain yes/no. Call it once at server start.

```lua
local HttpService = game:GetService("HttpService")

local PRODUCT_ID = "00000000-0000-0000-0000-000000000000"  -- from your cockpit
local USER_ID    = 1234567                -- the BUYER's Roblox UserId
local KEY        = "__UPDATR_LICENSE__"   -- replaced per buyer on download

local function isLicensed(): boolean
	local ok, res = pcall(function()
		return HttpService:RequestAsync({
			Url = "https://api.merithic.com/check",
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = HttpService:JSONEncode({
				user_id    = USER_ID,
				product_id = PRODUCT_ID,
				key        = KEY,
				game_id    = tostring(game.PlaceId),
			}),
		})
	end)
	if not ok or not res or not res.Success then
		-- Network trouble is NOT proof of piracy. Decide deliberately:
		-- fail open (keep running) or closed (stop). See "Failure modes".
		return true
	end
	return HttpService:JSONDecode(res.Body).success == true
end
```

{% hint style="info" %}
Send `game.PlaceId` as `game_id`. Updatr records it as the place a key was seen in, which is what powers leak alerts — one key running in a place you don't recognise is the signal.
{% endhint %}

### Why `/check` alone isn't enough

`/check` returns a boolean, and a boolean can be patched out. Someone who decompiles your script can replace `isLicensed()` with `return true`.

That's what `/unlock` is for.

## Step 2 — Ship the real thing behind `/unlock`

Rather than *asking permission* to run, fetch what you need to run **from the server, encrypted under the buyer's key**. There's nothing to patch: without a valid key the payload never decrypts.

Put whatever matters in the unlock payload:

* the actual source of your core module
* config a cracked copy can't guess
* a signing secret your systems check later

```lua
local payload = fetchUnlock()          -- see /unlock for the full routine
if not payload then
	warn("Updatr: could not verify this copy")
	return
end

-- payload.data is yours — you decide what's in it.
local core = loadstring(payload.data.source)()
core.start()
```

The response is sealed with HMAC-SHA256 and XOR-keystreamed. Verify the tag **before** you decrypt, and confirm the payload echoes the nonce you sent. [The scheme](/merithic-docs/api-reference/endpoints/unlock.md#the-scheme) has working code in Luau, Node, and Python.

{% hint style="danger" %}
Never log the decrypted payload or the key. `print(payload)` in a live game hands both to anyone reading the developer console.
{% endhint %}

## Step 3 — Auto-update

Two calls, in this order, and never the second without the first.

### 3a. Ask whether anything changed

```lua
local res = post("/version", {
	user_id = USER_ID, product_id = PRODUCT_ID, key = KEY,
	current = installedVersion,          -- e.g. "1.4.0"
})
if not res.update then return end        -- already current, stop here
```

`/version` is small and cheap. Call it at server start, or on a long interval.

### 3b. Pull the new build

```lua
local nonce   = randomNonce()            -- fresh every request
local payload = post("/update", {
	user_id = USER_ID, product_id = PRODUCT_ID, key = KEY,
	nonce = nonce, current = installedVersion,
})

-- Verify the tag, decrypt, and CHECK THE NONCE ECHO before trusting anything.
local tree = decryptAndVerify(payload, KEY, nonce)
if not tree or tree.nonce ~= nonce then
	warn("Updatr: update failed verification — keeping the current build")
	return
end

rebuildTree(tree.instances, script.Parent)
installedVersion = tree.version
```

{% hint style="warning" %}
`/update` returns the **entire build**. Polling it instead of `/version` wastes your rate limit and the buyer's bandwidth for no benefit.
{% endhint %}

### Rebuilding the tree

The payload's `instances` are plain tables: `ClassName`, `Name`, `Properties`, `Children`. Turn them into real Instances recursively, and destroy the old ones only **after** the new tree is built successfully.

A complete, working implementation ships with every product — open the **Code demos** tab on your product and copy the *Auto-update* demo, which includes pure-Lua SHA-256/HMAC so it runs with no dependencies.

## Failure modes — decide these on purpose

Every call can fail for reasons that have nothing to do with piracy: Roblox HTTP hiccups, a Updatr deploy, a buyer's server losing DNS for a moment.

| Situation                         | Sensible default                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------ |
| `/check` request errors           | **Fail open.** A network blip shouldn't brick a paying customer's game.              |
| `/check` returns `success: false` | **Fail closed.** That's a real answer: this key isn't valid.                         |
| `/unlock` fails                   | Stop. There's nothing to run without the payload.                                    |
| `/version` fails                  | Ignore it and keep running. You'll check again next start.                           |
| `/update` fails verification      | **Keep the build you have.** Never wipe a working install over an unverified update. |

{% hint style="info" %}
The single most common self-inflicted outage is failing closed on a network error. Your buyers experience it as "the thing I paid for randomly stopped working."
{% endhint %}

## Rate limits and etiquette

* Call `/check` and `/version` **once per server start**, not per player.
* Never call `/update` on a timer; gate it behind `/version`.
* Use a fresh `nonce` for every `/unlock` and `/update`. Reusing one defeats the replay protection.

## Testing before you ship

1. Turn on a **test token** on the product's Settings tab — it lets you exercise the endpoints without consuming a real license.
2. Publish to a private place first and confirm `/check` passes.
3. Upload a new version and confirm the update actually applies.
4. Try it with a deliberately wrong key and confirm you refuse to run.

## See also

{% content-ref url="/pages/JADHRnSQljLIJCQlnIy5" %}
[POST /check](/merithic-docs/api-reference/endpoints/check.md)
{% endcontent-ref %}

{% content-ref url="/pages/s4HsRum87vgVH4KC5lHW" %}
[POST /unlock](/merithic-docs/api-reference/endpoints/unlock.md)
{% endcontent-ref %}

{% content-ref url="/pages/CX3qolbkrLEBVX5AwLlq" %}
[POST /update](/merithic-docs/api-reference/endpoints/update.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://merithic.gitbook.io/merithic-docs/api-reference/integrating.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
