Testing & Examples

Upside includes a local game loader so you can embed a localhost game in the real parent shell before the game is approved for production discovery.

Upside test surface

Local Iframe Testing

The test route is:

text
https://upside.win/test/games/{BASE64_URL}

{BASE64_URL} is your game URL encoded as base64.

1

Start your game

bash
pnpm dev# game frontend available at http://localhost:3000
2

Encode the URL

bash
node -e 'console.log(Buffer.from("http://localhost:3000").toString("base64"))'
3

Open the Upside test URL

text
https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw
4

Send ready from the iframe

Your game should call window.parent.postMessage({ type: "ready" }, "https://upside.win") after its message listener is mounted.

5

Play a complete round

Verify placeBet, backend game logic, processPayout, parent modal, balance refresh, and match-history refresh.

Encoding Examples

Game URLBase64Test URL
http://localhost:3000aHR0cDovL2xvY2FsaG9zdDozMDAwhttps://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw
http://localhost:5000aHR0cDovL2xvY2FsaG9zdDo1MDAwhttps://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDo1MDAw
http://127.0.0.1:3000aHR0cDovLzEyNy4wLjAuMTozMDAwhttps://upside.win/test/games/aHR0cDovLzEyNy4wLjAuMTozMDAw
https://example.com/gameaHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1lhttps://upside.win/test/games/aHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1l

Manual Bridge Test

Add temporary logging during local development.

ts
window.addEventListener("message", event => { console.log("[upside message]", event.origin, event.data);});window.parent.postMessage({ type: "ready" }, "https://upside.win");

You should see a parent message containing balance, token, and locale. If you do not, verify that:

  • The iframe route actually loaded your game URL.
  • Your game sent ready after registering the listener.
  • Your browser did not block localhost networking.
  • Your message handler is checking https://upside.win, not your localhost origin.

Backend Smoke Test

Before wiring full gameplay, add a temporary authenticated route that calls checkAuth and getBalance.

ts
app.post("/api/upside-smoke-test", async c => { const b3Jwt = c.req.header("Authorization")?.replace(/^Bearer\s+/i, ""); if (!b3Jwt) return c.json({ error: "Missing token" }, 401); const auth = await upsideGameAction(c.env, b3Jwt, { trigger: "checkAuth" }); const balance = await upsideGameAction(c.env, b3Jwt, { trigger: "getBalance" }); return c.json({ auth, balance });});

Call it from your iframe with the token received from Upside:

ts
await fetch("/api/upside-smoke-test", { method: "POST", headers: { Authorization: `Bearer ${token}` },});

Complete Coin Flip Payloads

placeBet

json
{ "trigger": "placeBet", "gameType": "coin-flip", "sessionId": "coin-flip-2026-05-09T20-15-30Z-01", "gameId": "round-01", "betAmount": "1000000000000000000"}

processPayout Win

json
{ "trigger": "processPayout", "gameType": "coin-flip", "sessionId": "coin-flip-2026-05-09T20-15-30Z-01", "payoutAmount": "2000000000000000000", "result": "win", "gameData": { "prediction": "heads", "result": "heads" }}

processPayout Loss

json
{ "trigger": "processPayout", "gameType": "coin-flip", "sessionId": "coin-flip-2026-05-09T20-15-30Z-01", "payoutAmount": "0", "result": "loss", "gameData": { "prediction": "heads", "result": "tails" }}

Troubleshooting

Production /games/{slug} only works for approved games with an active record and workerUrl. Use /test/games/{BASE64_URL} until the game is registered.

Confirm your game posts { type: "ready" } after mounting, listens for messages before posting ready, and accepts event.origin === "https://upside.win".

Check the exact prehash: ISO timestamp, uppercase POST, /upside/game-actions, the raw JSON body string, and the same B3-JWT header value. Also confirm server time is accurate.

The service caps a bet at a percentage of the current game pool. Call getMaxBet before rendering high-stakes options.

Display the balance from the parent snapshot and disable bets above the current balance. Also handle this error server-side because balance can change between render and click.

Treat this as a reconciliation path, not as a second payout opportunity. Fetch history and return the already settled result to the player.

If frontend and backend are on different origins, allow your iframe origin. Do not open your backend to arbitrary origins in production.

Pre-Launch Review

Protocol Review

Verify the exact parent-child message behavior.

Learn More
Security Review

Check auth, idempotency, CORS, logging, and fairness requirements.

Learn More
Ask a question... ⌘I