Dashboard

Roblox

The Roblox integration handles verification, role binds (Discord roles โ†” Roblox group ranks/gamepasses/badges), playtime XP, and automatic promotions in your Roblox group when members hit rank thresholds.

Verification

Members link their Roblox account to their Discord account via OAuth. One click for the user; no verification codes, no bio-editing, no botting.

Requirements: register a Roblox OAuth application at create.roblox.com/credentials and enter its Client ID + Secret in the dashboard.

Role binds

Automatically assign Discord roles based on the member's state in your Roblox group:

  • Group rank binds โ€” "members at rank 100+ get the Officer role"
  • Gamepass binds โ€” "members who own the VIP gamepass get the VIP role"

Configured in the Role Binds tab of the Roblox dashboard. Runs when a member first links, then again on demand via /roblox sync or the Sync button.

Playtime XP

Players earn XP passively while active in your Roblox game. Default rate: 1 XP per 10 minutes of active playtime. Idle players (no input for 2+ minutes) don't accumulate.

XP flows into the ranks system โ€” see Ranks & XP for the full explanation of divisions, ranks, and auto-promotion.

Roblox group rank promotion

When Mira auto-promotes a member in Discord AND that rank has a robloxGroupRank value set, Mira also promotes the member in your actual Roblox group via the Open Cloud Groups API.

Prerequisites:
  • A Roblox API key with group:write scope for your group, generated at create.roblox.com/credentials
  • The API key owner's Roblox account must be in the group at a rank higher than the highest rank Mira will grant (Roblox's API can't promote to a rank โ‰ฅ the caller's own)
  • The Universe ID and Group ID must match the ones set in the dashboard

Bridge script

Paste this ServerScript into ServerScriptService in your Roblox game. Set BRIDGE_URL to your Mira domain and BRIDGE_KEY to the value from your dashboard (Roblox tab โ†’ Bridge โ†’ Reveal key).

-- =============================================================
-- MIRA BRIDGE โ€” Playtime โ†’ XP
-- Paste into ServerScriptService. Set the constants below.
-- =============================================================

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

-- ==== CONFIG ====
local BRIDGE_URL   = "https://www.rbananz.xyz"           -- your Mira domain
local BRIDGE_KEY   = "PASTE_YOUR_BRIDGE_KEY_HERE"        -- Dashboard โ†’ Roblox โ†’ Bridge key
local UNIVERSE_ID  = tostring(game.GameId)               -- auto-detected
local HEARTBEAT_S  = 30
local IDLE_TIMEOUT = 120
-- ================

local sessionToken, sessionExpiresAt = nil, 0

local function fetchSessionToken()
    local ok, res = pcall(function()
        return HttpService:RequestAsync({
            Url = BRIDGE_URL .. "/api/roblox/bridge/session",
            Method = "POST",
            Headers = { ["X-Bridge-Key"] = BRIDGE_KEY, ["Content-Type"] = "application/json" },
            Body = "{}",
        })
    end)
    if not ok or not res.Success then
        warn("[Mira] session fetch failed"); return nil
    end
    local data = HttpService:JSONDecode(res.Body)
    sessionToken = data.token
    sessionExpiresAt = tick() + (data.expiresInSeconds or 3600) - 300
    return sessionToken
end

local function getSessionToken()
    if sessionToken and tick() < sessionExpiresAt then return sessionToken end
    return fetchSessionToken()
end

local state = {}
local function initPlayer(player)
    state[player.UserId] = { lastActive = tick(), pendingActive = 0 }
    local function onCharacter(character)
        local root = character:WaitForChild("HumanoidRootPart", 10)
        if not root then return end
        local lastPos = root.Position
        while character.Parent and state[player.UserId] do
            task.wait(5)
            if not root or not root.Parent then break end
            if (root.Position - lastPos).Magnitude > 1 then
                state[player.UserId].lastActive = tick()
                lastPos = root.Position
            end
        end
    end
    if player.Character then task.spawn(onCharacter, player.Character) end
    player.CharacterAdded:Connect(function(c) task.spawn(onCharacter, c) end)
    player.Chatted:Connect(function()
        if state[player.UserId] then state[player.UserId].lastActive = tick() end
    end)
end

Players.PlayerAdded:Connect(initPlayer)
Players.PlayerRemoving:Connect(function(p) state[p.UserId] = nil end)
for _, p in ipairs(Players:GetPlayers()) do initPlayer(p) end

RunService.Heartbeat:Connect(function(dt)
    local now = tick()
    for _, s in pairs(state) do
        if now - s.lastActive < IDLE_TIMEOUT then
            s.pendingActive = s.pendingActive + dt
        end
    end
end)

task.spawn(function()
    while true do
        task.wait(HEARTBEAT_S)
        local token = getSessionToken()
        if not token then continue end
        local players = {}
        for userId, s in pairs(state) do
            if s.pendingActive >= 1 then
                table.insert(players, {
                    robloxUserId = tostring(userId),
                    activeSeconds = math.floor(s.pendingActive),
                })
                s.pendingActive = s.pendingActive - math.floor(s.pendingActive)
            end
        end
        if #players == 0 then continue end
        local ok, res = pcall(function()
            return HttpService:RequestAsync({
                Url = BRIDGE_URL .. "/api/roblox/bridge/heartbeat",
                Method = "POST",
                Headers = {
                    ["Authorization"] = "Bearer " .. token,
                    ["Content-Type"] = "application/json",
                },
                Body = HttpService:JSONEncode({ universeId = UNIVERSE_ID, players = players }),
            })
        end)
        if ok and res.StatusCode == 401 then
            sessionToken, sessionExpiresAt = nil, 0
        elseif ok and res.StatusCode == 403 then
            warn("[Mira] universe mismatch โ€” check dashboard Universe ID")
        end
    end
end)

print("[Mira] bridge loaded โ€” heartbeat " .. HEARTBEAT_S .. "s, idle " .. IDLE_TIMEOUT .. "s")

Also required: in Studio, go to Game Settings โ†’ Security and enable Allow HTTP Requests. Save and publish. The script runs on every server instance.

Commands

  • /verify โ€” start the verification flow
  • /roblox whois @user โ€” look up someone's linked Roblox account
  • /roblox sync @user โ€” force resync of role binds for a user
  • /roblox setup โ€” quick setup wizard for the current server

Configuration

On the Roblox dashboard page, configure:

  • Verification โ€” verified role, verify channel, rename-on-verify
  • OAuth app โ€” Client ID and Client Secret from Roblox Creator Hub
  • Group sync โ€” group ID + rank mappings to Discord roles
  • Gamepass sync โ€” gamepass ID + Discord role
  • Playtime XP & group promotion โ€” Universe ID, Group ID, Group API key, XP rate
  • Bridge โ€” the API key your game uses to authenticate