Roblox
One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues and referrals in a Roblox experience, from server scripts with HttpService.
Roblox clients cannot call outside services; only server Scripts can, with HttpService. So
everything goes through the server: it reports what players do, reads their progress and hands
it to the client with a RemoteEvent or RemoteFunction. The secret key never leaves the
server.
The prompt
Paste this into your coding agent from the root of the Rojo (or Argon) project, or ask it to write the scripts for you to paste into Studio.
Add a reward system to this Roblox experience with Orishare: XP and levels, coins, a daily
streak with repair, badges, a starter quest, a weekly XP leaderboard, a weekly league and friend
referrals. Follow https://docs.orishare.com/docs/recipes/roblox.md and the pages it links, step
by step: sign up for Orishare yourself (no human needed), apply the game starter configuration in
the test environment, write the server module and scripts the recipe describes, test the exact HTTP calls
against the test environment, then give me the claim link, where to put the secret in Studio, and a short
list of what you built. The secret key goes only in Roblox Secrets, never in a script.What runs where
| Piece | Holds | Does |
|---|---|---|
Server ModuleScript Orishare | The secret key, from HttpService:GetSecret("orishare_key") | Every call to Orishare |
Server Script | Calls the module on join, match end, level clear; answers the client's remote calls | |
Client LocalScript | Nothing secret | Shows the HUD from what the server sends; asks for a repair or a referral code through a RemoteFunction |
The player's id in Orishare is roblox-<UserId>: stable, yours, and the same on every server.
Steps
-
Sign up and store the credentials with Start here for AI agents. Keep the test secret key and agent token in your own notes or
.envoutside the place file. -
Apply the game starter in the test environment: Game setup.
-
Put the key in Roblox Secrets. Creator Dashboard, your experience, Secrets: name
orishare_key, value the secret key, domainapi.orishare.com. In Studio, Game Settings, Security: turn on Allow HTTP Requests. (Local Studio testing reads secrets from Game Settings, Security, Secrets in the same format.) -
The server module (
ServerScriptService/Orishare):local HttpService = game:GetService("HttpService") local API = "https://api.orishare.com" local Orishare = {} local function request(method, path, body, idempotencyKey) local key = HttpService:GetSecret("orishare_key") local headers = { ["Authorization"] = key:AddPrefix("Bearer "), ["Content-Type"] = "application/json", } if idempotencyKey then headers["Idempotency-Key"] = idempotencyKey end local ok, res = pcall(HttpService.RequestAsync, HttpService, { Url = API .. path, Method = method, Headers = headers, Body = body and HttpService:JSONEncode(body) or nil, }) if not ok then return nil, res end local decoded = res.Body ~= "" and HttpService:JSONDecode(res.Body) or nil if not res.Success then return nil, decoded end return decoded end function Orishare.customerId(player) return "roblox-" .. player.UserId end -- One event per fact. Reuse the same id on a retry: Orishare answers "duplicate". -- JSONEncode turns an empty table into [] (an array), which the API refuses with 400 -- "expected record, received array": leave properties out when there are none. function Orishare.track(player, name, properties, id) local body = { id = id or HttpService:GenerateGUID(false), name = name, customer_id = Orishare.customerId(player), mode = "sync", } if properties and next(properties) ~= nil then body.properties = properties end return request("POST", "/v1/events", body) end -- The internal cust_ id (created on first use), cached per server. local custIds = {} function Orishare.customer(player) if custIds[player.UserId] then return custIds[player.UserId] end local c = request("POST", "/v1/customers", { external_id = Orishare.customerId(player) }, "customer-" .. player.UserId) if c then custIds[player.UserId] = c.id end return custIds[player.UserId] end -- Player reads (level, streak, badges, quests, leaderboard, league) are customer routes: the -- server mints a session for the player and calls them with the PUBLISHABLE key and that token. -- The token stays on the server. Sessions last 15 minutes; mint a new one when it expires. local PUBLISHABLE_KEY = "opk_..." -- not a secret; use the live environment's key in the published game local sessions = {} local function session(player) local s = sessions[player.UserId] if s and s.expires > os.time() + 30 then return s.token end local r = request("POST", "/v1/customer_sessions", { grant_type = "secret_key", customer_id = Orishare.customer(player) }, HttpService:GenerateGUID(false)) if not r then return nil end sessions[player.UserId] = { token = r.customer_token, expires = os.time() + 14 * 60 } return r.customer_token end -- path: "balances", "levels", "streaks", "badges", "quests", "leaderboard_positions", "leagues" function Orishare.read(player, path) local token = session(player) if not token then return nil end local ok, res = pcall(HttpService.RequestAsync, HttpService, { Url = API .. "/v1/customer/" .. path, Method = "GET", Headers = { ["Authorization"] = "Bearer " .. PUBLISHABLE_KEY, ["X-Orishare-Customer-Token"] = token }, }) if not ok then return nil end if res.StatusCode == 401 then sessions[player.UserId] = nil end -- expired: mint again next call if not res.Success then return nil end return HttpService:JSONDecode(res.Body) end return OrishareSecret:AddPrefixbuilds the header without the key ever being a Lua string. Requests are limited per server (500 a minute): send one event per player action that matters, not per frame. -
The server script (
ServerScriptService/Rewards):local Players = game:GetService("Players") local Orishare = require(script.Parent.Orishare) Players.PlayerAdded:Connect(function(player) -- once per UTC day per player is enough for the daily streak local t = os.date("!*t") local day = string.format("%04d-%02d-%02d", t.year, t.month, t.day) Orishare.track(player, "session.started", {}, "session-" .. player.UserId .. "-" .. day) end) -- Call from your round logic: one id per player per match. local function onMatchEnded(matchId, player, score, won) Orishare.track(player, "match.completed", { score = score, won = won }, "match-" .. matchId .. "-" .. player.UserId) endSend
level.completedwith{ level = n }when a player clears a level. Then read the player's progress for the HUD withOrishare.read(player, "levels")(and"balances","streaks", ...) and send the fields to the client with aRemoteEvent. The fields are in Game setup, server reads. -
Player actions from the server. The client asks through a
RemoteFunction; the server acts for that player only:- Repair a broken streak with coins, when the
streaksread showsrepair.available:POST /v1/streaks/{id}/repairs({id}is thedaily_playprogram id, fromGET /v1/programs?limit=100) with{ customer_id = Orishare.customer(player), charge = true }and anIdempotency-Key. This is the server route, for the secret key the server holds; do not usePOST /v1/customer/streaks/repairsfrom Roblox (it needs a player session and answers401 invalid_customer_sessionto a secret key). - A referral code to share:
POST /v1/referral_codeswith{ program_id = "<invite_friends program id>", customer_id = Orishare.customer(player) }. A new player types a friend's code: the server sends it toPOST /v1/customer/referral_claims{ code = ... }with that player's session (publishable key and token, likeOrishare.read). - When the new player has really played, the server sends
referral.qualified(Game setup, referrals). It pays automatically after a one-day cooling-off; only a referral over the recipe's limits waits for a review with the agent token (the game's secret key cannot approve it). Never trust a client value for anything but the choice to act: the server decides who the player is (player.UserId), never an id the client sent.
- Repair a broken streak with coins, when the
-
Test the exact calls of the module from a terminal against the test environment (the module is a thin wrapper over them): Game setup, test. Then press Play in Studio with HTTP requests allowed.
-
Go live with Game setup, go live: put the live secret key in Roblox Secrets for the published experience.
Notes
HttpServicerequests only work from the server; aLocalScriptcall fails.- Do not show a coin balance the client computed; show what the server read.
- A display name other players see (
PATCH /v1/customer/social_profile, leaderboard pseudonyms) must go through Roblox text filtering (TextService:FilterStringAsync) before you send it. - A
mode: "sync"event can answer202withfallback: trueunder load: it is recorded and evaluated shortly, so read the HUD again a moment later instead of trusting the answer's effects. - Roblox players have no push device for Orishare: push messages to them are
suppressedwithno_device. Show reminders in game from thestreaksread (at_risk_at). Email works without a device: if the player gives you an address and agrees, your server records both withPOST /v1/customers/{id}/notification_contact(secret key,cust_id){ "email": "[email protected]", "email_consent": "opted_in", "locale": "en" }, and the message types get an email route ({ "channel": "email", "path": "builtin" }inroutes: send the full list withPATCH /v1/notification_message_types/{id}andIf-Match: "<revision>"in a test environment, or as the change set commandnotification_message_type.updatewithkeyandexpected_versionin a live environment). That is enough: the defaultemail_consent_modeisopt_in, and the recordedopted_inis that consent. Check that collecting an address fits Roblox's own rules for your game before you ask for one. Real email goes out only after a person claims the project; see Notifications. - Shop, items, energy, chests and the battle pass: Game economy.
React Native and Expo
One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues, referrals and push in a React Native or Expo app.
Firebase app
One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues and referrals in a Firebase app, through Cloud Functions that hold the secret key.