Lyzn
Whitelist
Self-hosted key system — no Luarmor. Generate, HWID-lock, ban, expire.
Connection
Worker URL (base, no trailing slash)
Admin Token (the ADMIN_TOKEN secret)
Connect & Load
Save
Setup — deploy the worker (one time)
Cloudflare →
Workers & Pages → Create
→ paste the
Worker code
below → Deploy (name it e.g.
lyzn-wl
).
Cloudflare →
Storage & Databases → KV
→ create a namespace. Then worker →
Settings → Bindings
→ add
KV Namespace
, variable name exactly
KEYS
.
Worker →
Settings → Variables
→ add
Secret
ADMIN_TOKEN
= a long random string, then put the same value in
Connection
above.
In the worker code, paste your
placeId → script URL
lines into the
GAMES
map, then redeploy.
Hand buyers the
Loader
snippet (edit the URL) with their key.
Worker code — paste into Cloudflare
📋 Copy
// ============================================================ // Lyzn Whitelist Worker — self-hosted key auth (Cloudflare KV) // No Luarmor. Handles: key generation/management (admin) + loader auth. // // SETUP (one time): // 1. Cloudflare → your worker → Settings → Variables → KV Namespace Bindings // Create a KV namespace (e.g. "LYZN_KEYS") and bind it as: KEYS // 2. Add a Secret: ADMIN_TOKEN = <a long random string> (panel uses this) // 3. Paste your placeId -> script URL map into GAMES below. // 4. Deploy. Point your Lua loaders at this worker. // ============================================================ // placeId (string) -> raw script URL. Paste your existing GAMES map here. const GAMES = { // "18336921544": "https://raw.githubusercontent.com/.../ultbball.lua", // "105935731955936": "https://raw.githubusercontent.com/.../ot7.lua", }; const DISCORD_INVITE = "https://discord.gg/SHwSt9ZyJk"; const CORS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, x-admin-token", "Access-Control-Max-Age": "86400", }; const KEY_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; function genKey(len = 32) { const bytes = crypto.getRandomValues(new Uint8Array(len)); let s = ""; for (const b of bytes) s += KEY_CHARS[b % KEY_CHARS.length]; return s; } function json(obj, status = 200) { return new Response(JSON.stringify(obj), { status, headers: { "Content-Type": "application/json", ...CORS }, }); } function luaKick(msg) { const safe = String(msg).replace(/"/g, '\\"'); return new Response( `game:GetService("Players").LocalPlayer:Kick("\\n\\n[Lyzn Hub]\\n\\n${safe}\\n\\nDiscord: ${DISCORD_INVITE}")`, { headers: { "Content-Type": "text/plain" } } ); } function luaEscape(s) { return String(s || "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]/g, ""); } async function readKey(env, key) { const raw = await env.KEYS.get(key); return raw ? JSON.parse(raw) : null; } async function writeKey(env, key, data) { await env.KEYS.put(key, JSON.stringify(data)); } // ---------- loader bootstrap (served when no ?key given) ---------- function bootstrap(origin) { return `if not script_key or script_key == "" then game:GetService("Players").LocalPlayer:Kick("\\n\\n[Lyzn Hub]\\n\\nNo key set.\\nPut your key in the script_key variable, then execute again.\\n\\nDiscord: ${DISCORD_INVITE}") return end pcall(function() local HttpService = game:GetService("HttpService") local hwid = "unknown" pcall(function() if gethwid then hwid = gethwid() else hwid = game:GetService("RbxAnalyticsService"):GetClientId() end end) local execName = "Unknown" pcall(function() if identifyexecutor then execName = tostring((identifyexecutor())) end end) local qs = "?key=" .. HttpService:UrlEncode(script_key) .. "&place=" .. tostring(game.PlaceId) .. "&hwid=" .. HttpService:UrlEncode(tostring(hwid)) .. "&exec=" .. HttpService:UrlEncode(execName) loadstring(game:HttpGet("${origin}/" .. qs))() end)`; } export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; if (request.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS }); if (!env.KEYS) return json({ error: "KV namespace 'KEYS' is not bound to this worker" }, 500); // =========================================================== // ADMIN API (token-gated) — used by the HTML panel // =========================================================== if (path.startsWith("/admin/")) { const token = request.headers.get("x-admin-token") || ""; if (!env.ADMIN_TOKEN || token !== env.ADMIN_TOKEN) return json({ error: "unauthorized" }, 401); const now = Math.floor(Date.now() / 1000); // ---- create N keys ---- if (path === "/admin/create" && request.method === "POST") { const b = await request.json().catch(() => ({})); const amount = Math.min(Math.max(parseInt(b.amount) || 1, 1), 100); const days = parseInt(b.days) || 0; // 0 = lifetime const note = String(b.note || "").slice(0, 120); const discord = String(b.discord || "").slice(0, 40); const expires = days > 0 ? now + days * 86400 : 0; const made = []; for (let i = 0; i < amount; i++) { const k = genKey(); await writeKey(env, k, { discord, hwid: "", created: now, expires, note, banned: false, execs: 0 }); made.push(k); } return json({ ok: true, keys: made, expires }); } // ---- list all keys (paged through KV) ---- if (path === "/admin/list") { const out = []; let cursor; do { const l = await env.KEYS.list({ cursor, limit: 1000 }); for (const it of l.keys) { const d = await readKey(env, it.name); if (d) out.push({ key: it.name, ...d }); } cursor = l.list_complete ? null : l.cursor; } while (cursor); out.sort((a, b) => (b.created || 0) - (a.created || 0)); return json({ ok: true, count: out.length, keys: out }); } // ---- update / delete a single key ---- if (path === "/admin/update" && request.method === "POST") { const b = await request.json().catch(() => ({})); const key = String(b.key || ""); if (b.action === "delete") { await env.KEYS.delete(key); return json({ ok: true, deleted: key }); } const d = await readKey(env, key); if (!d) return json({ error: "key not found" }, 404); if (b.action === "ban") d.banned = true; else if (b.action === "unban") d.banned = false; else if (b.action === "resethwid") d.hwid = ""; else if (b.action === "setdays") { const days = parseInt(b.days) || 0; d.expires = days > 0 ? now + days * 86400 : 0; } else if (b.action === "adddays") { const days = parseInt(b.days) || 0; const base = (d.expires && d.expires > now) ? d.expires : now; d.expires = base + days * 86400; } else if (b.action === "setdiscord") d.discord = String(b.discord || "").slice(0, 40); else if (b.action === "setnote") d.note = String(b.note || "").slice(0, 120); else return json({ error: "unknown action" }, 400); await writeKey(env, key, d); return json({ ok: true, key, data: d }); } // ---- stats ---- if (path === "/admin/stats") { let total = 0, active = 0, banned = 0, expired = 0, lifetime = 0; const now2 = Math.floor(Date.now() / 1000); let cursor; do { const l = await env.KEYS.list({ cursor, limit: 1000 }); for (const it of l.keys) { const d = await readKey(env, it.name); if (!d) continue; total++; if (d.banned) banned++; else if (d.expires > 0 && now2 > d.expires) expired++; else { active++; if (d.expires === 0) lifetime++; } } cursor = l.list_complete ? null : l.cursor; } while (cursor); return json({ ok: true, total, active, banned, expired, lifetime }); } return json({ error: "unknown admin route" }, 404); } // =========================================================== // LOADER AUTH — the Lua executes against this // =========================================================== const key = url.searchParams.get("key"); const placeId = url.searchParams.get("place"); const hwid = (url.searchParams.get("hwid") || "").trim(); // no key -> hand back the bootstrap loader if (!key || key.trim() === "") { return new Response(bootstrap(url.origin), { headers: { "Content-Type": "text/plain" } }); } const trimmed = key.trim(); const d = await readKey(env, trimmed); if (!d) return luaKick("Your key is invalid."); if (d.banned) return luaKick("This key has been banned."); const now = Math.floor(Date.now() / 1000); if (d.expires > 0 && now > d.expires) return luaKick("Your key has expired.\\nRenew in the Discord."); // HWID lock: bind on first use, reject a different device after if (hwid && hwid !== "unknown") { if (!d.hwid) { d.hwid = hwid; } else if (d.hwid !== hwid) return luaKick("HWID mismatch.\\nThis key is locked to another device.\\nReset HWID in the Discord."); } d.execs = (d.execs || 0) + 1; d.lastExec = now; ctx.waitUntil(writeKey(env, trimmed, d)); const scriptUrl = GAMES[placeId]; if (!scriptUrl) return luaKick("No script is available for this game."); let script = ""; try { const res = await fetch(scriptUrl); if (!res.ok) return luaKick("Script fetch failed. Try again shortly."); script = await res.text(); } catch (_) { return luaKick("Script fetch error. Try again shortly."); } // expose key info to the script (for a profile/expiry card if it wants it) const inject = `LyznKey = "${luaEscape(trimmed)}"\n` + `LyznExpiry = ${d.expires}\n` + `LyznDiscord = "${luaEscape(d.discord || "")}"\n`; return new Response(inject + script, { headers: { "Content-Type": "text/plain" } }); }, };
Buyer loader — edit URL + key
📋 Copy
--[[ Lyzn Whitelist — buyer loader This is what you hand to a whitelisted user. They paste their key, execute. The worker's / route serves a bootstrap that reads script_key, grabs their HWID, calls /auth, and runs the returned game script (or kicks them). Replace the URL with YOUR whitelist worker's base URL. ]] script_key = "PASTE-YOUR-KEY-HERE" loadstring(game:HttpGet("https://lyzn-wl.yoursub.workers.dev/"))()
–
Total
–
Active
–
Expired
–
Banned
Generate Keys
Days (0=lifetime)
Amount
Note (optional)
Discord ID (optional)
Generate
📋 Copy all
Keys
↻ Refresh
Key
Discord
HWID
Expires
Execs
Status
Actions
Connect to load keys.