Create Your Own Service

Build a key-generation service of your own and plug it into KeySystem.API

How it plugs in

Add an entry with Type = "custom" to KeySystem.API, next to (or instead of) Platoboost/PandaAuth/Jnkie. Put whatever fields you want in it — ServiceId, a name, anything — you'll get them back inside your functions.

CheckKey(key, ctx) is required. It's called with what the player typed, and must return valid, reason.

GetKeyLink(ctx) is optional. It powers the "Get a key" button, runs when the player clicks it, and must return a URL, or nil, "reason" on failure.

ctx is every field you put on the entry, plus ctx.Request — the executor's HTTP function (request/http_request/syn.request/http.request, whichever exists), or nil if the executor has none.

Don't just compare a string

The whole script — including anything you hardcode inside CheckKey — runs on the player's own device. A fixed key, or a secret used to compute one locally, can be read straight out of the running script (even minified/obfuscated) with basic executor tools. Whatever decides "valid" has to live on a server you control, not inside the function.

Recommended design: sessions

Give your backend three states for a key request: pending → issued → consumed.

1. GetKeyLink asks your backend to start a session. It replies with a session_id and a url (your ad page/whatever, with the session id attached).

2. The player completes your flow on that page. Your backend generates the key there and marks the session issued.

3. CheckKey sends the session_id together with the key. Your backend checks they match an issued session and, if so, marks it consumed before answering.

Someone capturing that last request and replaying it gets nothing: the session is already consumed, so your backend refuses it a second time. Give pending sessions a short expiry too, so an unused one can't be used to skip your flow later.

Note this only protects the acquisition step. Whether the issued key itself should keep validating afterwards (for example with SaveKey = true, so the player isn't asked again next session) is a separate decision your backend makes — e.g. by giving the key its own expiry, independent of the session that produced it.

Example

This is a starting point, not a drop-in service — the URLs are placeholders for your own backend.

local HttpService = game:GetService("HttpService") local Ophyn = loadstring(game:HttpGet('https://ophyn.space/interface'))() local session -- remembers the session between GetKeyLink and CheckKey Ophyn.new({ Title = "Ophyn", Description = "Key System", Logo = "rbxassetid://111673746737789", Theme = "Plant-Dark", Folder = "Ophyn-KSY", getkey = true, discord_link = "discord.gg/yourcode", Discord = "true", Website = "false", KeySystem = { SaveKey = true, API = { { -- your own service Type = "custom", ServiceId = "my-service", -- read back as ctx.ServiceId -- Called when the player clicks "Get a key". -- Your backend creates a session and hands back where to send them. GetKeyLink = function(ctx) if not ctx.Request then return nil, "your executor doesn't support HTTP requests." end local ok, res = pcall(ctx.Request, { Url = "https://yoursite.com/api/session/start", Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = HttpService:JSONEncode({ service = ctx.ServiceId }), }) if not ok or type(res) ~= "table" or res.StatusCode ~= 200 then return nil, "Could not start the key flow" end local data = HttpService:JSONDecode(res.Body) session = data.session_id -- kept for CheckKey below return data.url end, -- Called with whatever the player typed into the key box. CheckKey = function(key, ctx) if not session then return false, "Get a key first" end if not ctx.Request then return false, "your executor doesn't support HTTP requests." end local ok, res = pcall(ctx.Request, { Url = "https://yoursite.com/api/session/validate", Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = HttpService:JSONEncode({ session_id = session, key = key, }), }) if not ok or type(res) ~= "table" or res.StatusCode ~= 200 then return false, "Could not reach the key server" end local data = HttpService:JSONDecode(res.Body) return data.valid == true, data.reason end, }, }, }, Callback = function(key) -- your script here end, })

What your backend needs

• Start a session — create a session_id, store it as pending with an expiry, return it with a URL.

• Issue the key — on your flow page, once the player finishes, generate the key, store it against that session_id, mark it issued.

• Validate — check the session_id is issued and the key matches; mark it consumed and answer valid = true. Anything else (unknown, pending, already consumed, expired, wrong key) answers valid = false with a reason.

What this doesn't solve

This stops a captured request from being replayed to skip your flow. It doesn't stop someone who legitimately has a still-valid key from handing it to someone else — nothing running on the player's device can prevent that, because the player's device is what you'd have to trust to enforce it. If key-sharing matters to you, that's a separate decision to make in how long a key stays valid and what you tie it to.