> ## Documentation Index
> Fetch the complete documentation index at: https://gabibou-ui.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# External key service from a Roblox server

> Keep a provider credential on the server and adapt its HTTP contract to a key gate.

Use this pattern when you need a Roblox experience to validate with an external key service while keeping provider credentials off the client. The client sends the entered key to a `RemoteFunction`; a server `Script` sends an HTTPS request to your provider and returns a boolean result. This is for Roblox experiences with server scripts. Executor-only scripts without a server should use the provider's client SDK adapter or their own backend.

This is a generic template, not a built-in integration with any provider. It expects your endpoint to accept a JSON body containing `key` (and optionally `userId`) and to return JSON with a boolean `valid` field. Change the URL, request fields, authorization header, and response parsing to match your provider's official API. No request is sent by this example until you configure a real endpoint.

## Server script

Place this code in `ServerScriptService`. In Creator Hub, add an HTTP secret named `KeyProviderToken` for your provider's domain, and enable **Allow HTTP Requests** in Experience Settings. Keep any owner or admin credential in the server-side secret store; never paste it into the client `LocalScript`.

```lua theme={null}
-- Script in ServerScriptService. HTTP and provider credentials stay on the server.
-- Replace the endpoint and request/response fields with your provider's documented contract.
-- Create the KeyProviderToken secret for your provider's domain in the Creator Dashboard.
-- Allow HTTP Requests in Experience Settings.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local HttpService = game:GetService("HttpService")

local remoteName = "GabibouValidateKey"
local endpoint = "https://replace-with-your-provider.example/v1/validate"
local secretName = "KeyProviderToken"
local includeRobloxUserId = false

assert(not ReplicatedStorage:FindFirstChild(remoteName), remoteName .. " already exists")

local attempts = {}
local remote = Instance.new("RemoteFunction")
remote.Name = remoteName

local function requestProvider(key, player)
	local body = {key = key}
	-- Only send this identifier if your provider needs Roblox account binding.
	if includeRobloxUserId then body.userId = player.UserId end

	local credential = HttpService:GetSecret(secretName)
	return HttpService:RequestAsync({
		Url = endpoint,
		Method = "POST",
		Headers = {
			["Content-Type"] = "application/json",
			Authorization = credential:AddPrefix("Bearer "),
		},
		Body = HttpService:JSONEncode(body),
	})
end

remote.OnServerInvoke = function(player, key)
	local now = os.clock()
	if attempts[player] and now - attempts[player] < 2 then
		return false, "Please wait before trying again."
	end
	attempts[player] = now
	if type(key) ~= "string" or #key == 0 or #key > 512 or not key:match("%S") then
		return false, "Key not accepted"
	end

	-- The expected provider response is JSON: {"valid": true} or {"valid": false}.
	-- Adapt the request and this field check to the provider; there is no universal key API.
	local requestOk, response = pcall(requestProvider, key, player)
	if not requestOk or type(response) ~= "table" or response.Success ~= true or type(response.Body) ~= "string" or #response.Body > 32768 then
		return false, "Validation service unavailable. Try again."
	end
	local decodeOk, result = pcall(HttpService.JSONDecode, HttpService, response.Body)
	if not decodeOk or type(result) ~= "table" or type(result.valid) ~= "boolean" then
		return false, "Validation service unavailable. Try again."
	end
	if result.valid ~= true then return false, "Key not accepted" end

	player:SetAttribute("GabibouAuthorized", true)
	return true
end

remote.Parent = ReplicatedStorage
Players.PlayerRemoving:Connect(function(player)
	attempts[player] = nil
end)

-- Check this server-owned attribute in every protected server action:
-- if player:GetAttribute("GabibouAuthorized") ~= true then return end
```

## Client gate

Place this in a `LocalScript`. The key travels through the `RemoteFunction`; the provider credential and HTTP call remain in the server script.

```lua theme={null}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UI = require(ReplicatedStorage:WaitForChild("GabibouUI"))
local validateKey = ReplicatedStorage:WaitForChild("GabibouValidateKey")

local session = UI:Launch({
    Id = "ExternalKeyExample",
    Title = "Garden Tools",
    KeySystem = {
        Title = "Access key",
        GetKey = {Url = "https://your-provider.example/get-key", Text = "Get key"},
        Validate = function(key)
            return validateKey:InvokeServer(key)
        end,
    },
    Tabs = {{Title = "Home", Icon = "home"}},
    OnReady = function(window)
        window:Notify({Title = "Ready", Content = "Access was granted.", Kind = "success"})
    end,
})
```

The key gate handles yielding validation and rejects a failed or malformed service response. The server example applies a simple two-second per-player cooldown and fails closed on network, HTTP, or JSON errors. Add provider-specific rate limits, request authentication, and server checks for every protected action that your experience needs; the client gate alone does not protect server behavior.

For Roblox details, see the Creator Hub pages for [in-game HTTP requests](https://create.roblox.com/docs/cloud-services/http-service) and [RemoteFunction](https://create.roblox.com/docs/reference/engine/classes/RemoteFunction). For a local server-owned list rather than an HTTP provider, see [Server validation](https://gabibou-ui.mintlify.site/examples/server-validation.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.