Web game
One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues and referrals in a browser game with a game server. What runs where, the code, and how to test it.
For a browser game in JavaScript or TypeScript (Phaser, PixiJS, three.js, plain canvas, an io-style game) that has a game server: Node, Deno, Bun, a serverless function or any backend that decides who won. Without any server, use the Firebase or Supabase recipe instead.
The prompt
Paste this into your coding agent from the root of the game's repository.
Add a reward system to this web game 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/web-game.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, integrate the game server and the browser client as the recipe says, test everything in
the test environment, then give me the claim link and a short list of what you built.
Never put a secret key (osk_) or agent token (opat_) in browser code.What runs where
| Piece | Holds | Does |
|---|---|---|
| Game server | Secret key (osk_), env var ORISHARE_SECRET_KEY | Reports what happened in a match (POST /v1/events), mints a customer session for a signed-in player |
| Browser | Publishable key (opk_), the player's customer session token in memory | Reads the player's progress (the /v1/customer/... reads), repairs a streak, makes a referral code, claims a quest |
Match results come from the server because the server is the authority: its events are
merchant_asserted and never capped. The browser may also report light activity
(session.started) with the player's session; those count up to the per-day caps of the
game starter.
Steps
-
Sign up and store the credentials. Follow Start here for AI agents: you get a test and a live environment, keys, an agent token and a claim link. Put the secret key and agent token in the server's environment (a
.envthe repository ignores). -
Apply the game starter in the test environment: Game setup. One change set creates the currencies, levels, streak, badges, quest, leaderboard and referral program; the league is one more call.
-
Keys. The secret key from the signup reports events and mints customer sessions. A key you create later needs explicit scopes for that (Game setup, session key).
-
Server: report matches. When a match ends, for each player:
// server/orishare.js const API = process.env.ORISHARE_API ?? 'https://api.orishare.com'; const KEY = process.env.ORISHARE_SECRET_KEY; // osk_ from the signup export async function reportMatch({ matchId, playerId, score, won, endedAt }) { const res = await fetch(`${API}/v1/events`, { method: 'POST', headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ // One id per fact: a retry with the same id is answered `duplicate`, never counted twice. id: `match-${matchId}-${playerId}`, name: 'match.completed', customer_id: playerId, // your own player id (the external id) occurred_at: endedAt, // when the match ended (ISO 8601): stored with the match, the same // on every retry, and never in the future (clamp to now on clock skew) properties: { score, won }, mode: 'sync', // evaluate now and return the effects (XP, coins, badges) }), }); // 200 with status "duplicate": already recorded. 409 event_id_conflict: same id, other body. if (!res.ok) throw new Error(`orishare ${res.status}`); const body = await res.json(); // Under load sync falls back to 202 with `fallback: true` and no effects: the event is still // recorded and evaluated shortly; read the player's state again instead of the effects. return body; }Send
level.completed(properties: { level }) the same way when a player clears a level. Retry a failed send with the sameidand the same body (occurred_atincluded: store it with the match rather than computing it per attempt) (a different body under the same id is409 event_id_conflict); see Stable retry ids./v1/eventsor/v1/match_results? Both pay the same game starter rules: a match result admits each player as amatch.completedevent withproperties.won(send"won"per player) and the profile's metrics, and the starter's win rules, badge and quest step readwon. UsePOST /v1/eventsas above to start: no setup, andmode: "sync"returns the effects (XP, coins, badges). Switch toPOST /v1/match_resultsonce you want each result checked against a match profile (metric bounds, duration, player count, rate limits, held reviews): it needs amatch_profile.createchange set first, and its answer carries each player's admissionstatus, not the XP or coin effects, so read the player's balances and levels afterwards. The Game economy recipe uses match results. Send each match through one of the two, never both, or it pays twice. -
Server: give the browser a session. After your own login decides who the player is, resolve the internal customer id and mint a session. Return only the token:
export async function customerSession(playerId) { const auth = { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' }; const customer = await fetch(`${API}/v1/customers`, { method: 'POST', headers: { ...auth, 'Idempotency-Key': `customer-${playerId}` }, body: JSON.stringify({ external_id: playerId }), }).then((r) => r.json()); // 201 created or 200 existing, either way { id: 'cust_...' } const session = await fetch(`${API}/v1/customer_sessions`, { method: 'POST', headers: { ...auth, 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ grant_type: 'secret_key', customer_id: customer.id }), }).then((r) => r.json()); // customer_id: the player's cust_ id. Customer event ids are already per player. return { token: session.customer_token, expires_at: session.expires_at, customer_id: customer.id, }; }Expose it as an endpoint of your game server (for example
/api/orishare-session, method POST) that only a signed-in player can call. -
Browser: read and act. Every customer call carries the publishable key and the token:
// client/orishare.js const API = 'https://api.orishare.com'; const PUBLISHABLE_KEY = 'opk_...'; // public config const SESSION_ENDPOINT = '/api/orishare-session'; // your game server, step 5 let token = null; // memory only: never localStorage, cookies or URLs async function session() { if (!token) token = (await (await fetch(SESSION_ENDPOINT, { method: 'POST' })).json()).token; return token; } export async function customer(path, init = {}) { const res = await fetch(API + '/v1/customer/' + path, { ...init, credentials: 'omit', headers: { Authorization: `Bearer ${PUBLISHABLE_KEY}`, 'X-Orishare-Customer-Token': await session(), 'Content-Type': 'application/json', ...(init.method && init.method !== 'GET' ? { 'Idempotency-Key': crypto.randomUUID() } : {}), ...init.headers, }, }); if (res.status === 401) token = null; // expired: get a new one on the next call return res.json(); } // HUD: customer('levels'), customer('balances'), customer('streaks'), customer('badges'), // customer('quests'), customer('leagues'), customer('referral_codes')The reads and the player actions (repair, referral code, quest claim) are in Game setup, player screens. Sessions last 15 minutes by default: renew or mint again (Session lifetime).
-
Allow the game's origin. A browser on another origin than your server is refused until its origin is allowed. Origins given as
allowed_originsat signup are already set (GET /v1/client_configuration); to add one,PATCH /v1/client_configurationwith the test secret key (Hosted widget, allow your page's origin shows the body and theIf-Matchheader).http://localhostworks in the test environment. -
Test in the test environment with Game setup, test: send a few matches for a test player, then read the player's state.
-
Go live with Game setup, go live.
Optional
- The hosted widget shows the whole rewards panel with one script tag: Hosted widget.
- Notifications (streak at risk, level up): Game setup, notifications.
- Shop, items, energy, chests, battle pass: Game economy.
Game setup
The configuration every recipe applies: XP and 100 levels, coins, a daily streak with repair, badges with rarity, a starter quest, a weekly XP leaderboard, a weekly league, referrals, notifications and friends. Exact calls, caps, player screens and tests.
Unity
One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues and referrals in a Unity game, with UnityWebRequest and a customer session.