-- Server-validated profile synchronization sample -- Redact Studios / trulyagents -- -- This standalone sample demonstrates a server-only Roblox-to-service sync. -- Production endpoints and credentials are intentionally omitted. local HttpService = game:GetService("HttpService") local Players = game:GetService("Players") local CONFIG = { Endpoint = "https://example.com/api/roblox/profile-sync", Authorization = "Bearer REPLACE_WITH_A_SERVER_SECRET", MaxAttempts = 3, BaseRetryDelay = 1.5, } local ALLOWED_POSITIONS = { PG = true, SG = true, SF = true, PF = true, C = true, } local function readValue(container, name, fallback) local valueObject = container and container:FindFirstChild(name) if not valueObject then return fallback end if valueObject:IsA("StringValue") or valueObject:IsA("NumberValue") or valueObject:IsA("IntValue") then return valueObject.Value end return fallback end local function cleanText(value, maxLength) local text = tostring(value or "") text = text:gsub("[%c]", ""):sub(1, maxLength) return text end local function buildPayload(player) local data = player:FindFirstChild("Data") if not data then return nil, "profile data is not loaded" end local position = cleanText(readValue(data, "Position", "N/A"), 3) if not ALLOWED_POSITIONS[position] then position = "N/A" end return { robloxUserId = player.UserId, robloxUsername = player.Name, displayName = player.DisplayName, loreName = cleanText(readValue(data, "LoreName", player.DisplayName), 40), position = position, archetype = cleanText(readValue(data, "Archetype", "Unassigned"), 40), height = cleanText(readValue(data, "Height", "N/A"), 16), overall = math.clamp(tonumber(readValue(data, "Overall", 0)) or 0, 0, 99), updatedAt = DateTime.now().UnixTimestamp, } end local function sendPayload(payload) local encoded = HttpService:JSONEncode(payload) for attempt = 1, CONFIG.MaxAttempts do local ok, response = pcall(HttpService.RequestAsync, HttpService, { Url = CONFIG.Endpoint, Method = "POST", Headers = { ["Authorization"] = CONFIG.Authorization, ["Content-Type"] = "application/json", }, Body = encoded, }) if ok and response.Success then return true end if attempt < CONFIG.MaxAttempts then task.wait(CONFIG.BaseRetryDelay * (2 ^ (attempt - 1))) end end return false end local pendingSync = {} local function queueProfileSync(player) local userId = player.UserId pendingSync[userId] = (pendingSync[userId] or 0) + 1 local revision = pendingSync[userId] -- Debounce rapid stat changes into a single outbound request. task.delay(2, function() if pendingSync[userId] ~= revision or not player.Parent then return end local payload = buildPayload(player) if payload then sendPayload(payload) end end) end local function watchProfile(player) local data = player:WaitForChild("Data", 20) if not data then return end for _, child in data:GetChildren() do if child:IsA("ValueBase") then child.Changed:Connect(function() queueProfileSync(player) end) end end data.ChildAdded:Connect(function(child) if child:IsA("ValueBase") then child.Changed:Connect(function() queueProfileSync(player) end) queueProfileSync(player) end end) queueProfileSync(player) end Players.PlayerAdded:Connect(function(player) task.spawn(watchProfile, player) end) Players.PlayerRemoving:Connect(function(player) pendingSync[player.UserId] = nil end)