Testing & Examples
Test a local iframe game on Upside, verify the bridge, and debug common integration failures before launch.
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.
Local Iframe Testing
The test route is:
texthttps://upside.win/test/games/{BASE64_URL}
{BASE64_URL} is your game URL encoded as base64.
Start your game
bashpnpm dev# game frontend available at http://localhost:3000
Encode the URL
bashnode -e 'console.log(Buffer.from("http://localhost:3000").toString("base64"))'
Open the Upside test URL
texthttps://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw
Send ready from the iframe
Your game should call window.parent.postMessage({ type: "ready" }, "https://upside.win") after its message listener is mounted.
Play a complete round
Verify placeBet, backend game logic, processPayout, parent modal, balance refresh, and match-history refresh.
Encoding Examples
| Game URL | Base64 | Test URL |
|---|---|---|
http://localhost:3000 | aHR0cDovL2xvY2FsaG9zdDozMDAw | https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw |
http://localhost:5000 | aHR0cDovL2xvY2FsaG9zdDo1MDAw | https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDo1MDAw |
http://127.0.0.1:3000 | aHR0cDovLzEyNy4wLjAuMTozMDAw | https://upside.win/test/games/aHR0cDovLzEyNy4wLjAuMTozMDAw |
https://example.com/game | aHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1l | https://upside.win/test/games/aHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1l |
Manual Bridge Test
Add temporary logging during local development.
tswindow.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
readyafter 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.
tsapp.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:
tsawait 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.