Riz Games SDK · v1.0

One player identity. Any web stack.

Connect a Next.js, Svelte, Phaser, Unity WebGL, Godot or plain JavaScript game to verified sessions, analytics, rankings and RP.

Framework-free

One script tag. No dependencies, no build step.

Server trusted

Riz calculates rewards; games report events.

01

How it works

Your game runs in its own iframe, on your own hosting, at your own origin. It never receives a Riz credential and never calls the Riz API directly.

01

Launch

The player opens your game from its Riz page. Riz loads your deployed URL in the play wrapper.

02

Session

The SDK asks the wrapper for a run. Riz issues a short-lived token to the wrapper, not to your game.

03

Events

Your game reports gameplay events over postMessage. The wrapper forwards them with the player’s identity.

04

Reward

Riz validates every event and decides the score, the rank and the RP. Your build cannot grant a reward.

Why the bridge exists

Cookies and tokens cannot cross origins safely, and browser code cannot keep a secret. Putting the token in the Riz wrapper means you never handle player identity, never store a credential, and never have to be trusted with scoring.

02

Quick start

Four calls is a complete integration. Add the script, open a run, report progress, end the run.

1 · Load the SDK
<script src="https://games.riz.africa/riz-games-sdk.js"></script>

<!-- TypeScript projects can pull in the ambient types: -->
<!-- https://games.riz.africa/riz-games-sdk.d.ts -->
2 · Wire your game loop
// 1. Open the run. Resolves once Riz has issued a session.
const { player, game, capabilities } = await RizGames.init({
  gameKey: "riz_game_your_public_key"
});
showPlayerName(player.displayName);

// 2. Mark the start of a real run. Begins active-time measurement.
await RizGames.game.start();

// 3. Report progress as it happens.
await RizGames.score.submit(2400);
await RizGames.analytics.level.complete("level-1", { time: 42 });
await RizGames.analytics.track("shop_opened", { source: "lobby" });

// 4. Finish. Score, rank and RP come back from the Riz server.
const run = await RizGames.game.end(2400);
showResults(run.score, run.rpEarned, run.rank);

Copy a working integration

The reference game is a single HTML file using every API on this page: sessions, levels, achievements, leaderboards and error handling. Open it directly and it runs in sandbox mode; paste in your game key and it runs for real.

03

Engine builds: Unity, Godot, big downloads

A large build is playable minutes after the page loads. Tell Riz about that gap and the arcade renders its own loading screen around your build, exactly like a first-party Riz original.

Driving the Riz loading screen
// Unity WebGL, Godot, or any build with a long download.
// Works before initialize(). Report while you are still loading.
createUnityInstance(canvas, config, (fraction) => {
  RizGames.loading.progress(fraction);   // drives the Riz loading bar
}).then(() => {
  RizGames.loading.ready();              // dismisses the loading screen
  return RizGames.init({ gameKey: "riz_game_..." });
}).catch((error) => {
  RizGames.loading.failed(error.message); // shows the player a retry screen
});

Report from the first frame

Riz waits briefly after your page loads to see whether you claim the loading screen. Call loading.progress() as soon as your loader starts, not after the first asset lands.

Ship single-threaded WASM

The creator frame is not cross-origin isolated, so SharedArrayBuffer is unavailable. In Unity, that means disabling multithreading in the WebGL build settings.

04

Spend your event budget well

Events are network calls, not counters. A run accepts 500 of them, 120 per minute, which is enough for meaningful progression but not for a per-frame stream.

Reporting cadence
// WRONG: a 60fps loop burns the run's 500-event budget in seconds.
function onFrame() { RizGames.score.submit(score); }

// RIGHT: report on meaningful change, and always at the end.
function onCheckpoint() { RizGames.score.submit(score); }
function onGameOver()   { RizGames.game.end(score); }

// The live ceilings, sent by the server at handshake:
const { limits } = RizGames.getSession();
limits.maxEventsPerSession;  // 500
limits.maxEventsPerMinute;   // 120
limits.maxAdEventsPerSession; // 120, metered separately from gameplay

Budgets are published, not guessed

Read the live values from RizGames.getSession().limits. The wrapper sends the server's real numbers at handshake and the SDK self-throttles to them, so a busy moment queues instead of failing. Only the highest score of a run counts, so you never need to report every point.

05

Ask before you offer

What a game may do is set per game by Riz, not baked into the SDK build. Check the capability, then show the button.

Capability checks
// What this game is allowed to do is an operator setting, not a
// property of the SDK build. Ask before you offer the feature.
if (RizGames.supports("data.storage")) {
  await RizGames.data.setItem("save", JSON.stringify(state));
}

if (RizGames.supports("ads.rewarded")) {
  const ad = await RizGames.ads.rewarded({ placement: "continue" });
  if (ad.rewarded) grantExtraLife();   // never infer this from ad.completed
}

// A disabled capability rejects with capability_disabled. The method is
// always present, so this never throws a TypeError.

A disabled capability is an error, not a missing method

Every module is always present on the SDK object. One that is not enabled rejects with capability_disabled, so a feature Riz turns off surfaces in your error branch instead of crashing your game loop.

06

Local development

You do not need a Riz session to build against the SDK. Opened outside the wrapper, it runs in demo mode instead of stalling on a handshake that will never complete.

Demo mode
// Opened outside the Riz wrapper (a local dev server, or the file
// straight from disk), the SDK runs in demo mode instead of hanging.
// Every call resolves locally and the console says so.
await RizGames.init({ gameKey: "riz_game_your_public_key" });

RizGames.environment;   // "local" while developing, "production" on Riz
RizGames.dev.isDemo();  // same question, as a boolean
RizGames.dev.log();     // every operation your build attempted

// Force demo mode on any host, including a staging deploy:
//   https://your-game.example.com/?rizLocalSdk=true

// Script the outcomes you cannot trigger on demand:
RizGames.dev.simulate({ ads: { outcome: "skipped", durationMs: 3000 } });
RizGames.dev.forceError("rate_limited");

// Force the strict behaviour when you want to test the real handshake:
await RizGames.init({ gameKey: "...", sandbox: false });

Nothing in demo mode is real

Events are logged locally, RP is computed with the real formula but never awarded, and the leaderboard comes back empty. Demo mode can reproduce every failure a live session can — no fill, a skipped ad, a full save quota, a cancelled purchase — so you can build the unhappy paths before you have a session. The console banner makes the mode unmistakable.

07

Delivery guarantees

Reporting an event is a network call across a postMessage bridge. The SDK handles the failure modes so your game loop does not have to.

Ordered delivery

Events leave in the order you called them, one in flight at a time, so a score can never overtake the game_ended that finalizes it.

Automatic retries

Transient failures back off exponentially. Your await resolves when the event really lands.

Exactly-once accounting

Every event carries an id that survives retries, so a redelivery is deduplicated rather than counted twice.

Survives a closed tab

A run abandoned mid-game is flushed through the wrapper with sendBeacon instead of being lost.

08

Supported events

Only documented event names are accepted. Extra metadata can support analytics, but it never directly grants RP.

game_loaded

Sent automatically once the SDK connects.

game_started

A real run has begun. Starts active-time measurement.

level_started

The player entered a level or round.

level_completed

A level ended successfully.

score_submitted

Updates the highest score in this run.

achievement_unlocked

Reports an achievement candidate.

game_paused / game_resumed

Sent on your call, and automatically when the tab is hidden.

session_heartbeat

Sent automatically. Measures active, non-idle in-game time.

tutorial_started / completed

Measures onboarding progression and completion.

checkpoint_reached

Records meaningful progression points.

item_collected / powerup_used

Measures feature and content usage.

custom_event

Records a creator-defined analytics event and metadata.

game_completed_percentage

Reports overall progression from 0 to 100.

first_frame_rendered

Sent once, when your build is actually playable. Measures time to first frame.

funnel_step

Records a named step in a progression funnel you define.

ad_* / banner_*

Sent by the SDK around an ad. Metered against a separate allowance, so ads never consume your gameplay budget.

data_saved / data_migrated

Sent by the data module when progress is written, or moved into an account on sign-in.

room_* / invite_* / content_shared

Multiplayer and social activity, for platform invite and join surfaces.

purchase_* / entitlement_consumed

Purchase lifecycle. Grant items from entitlements(), never from the purchase call alone.

game_ended

Finalizes the run, the ranked score and any eligible RP. Returns rewardsPaused when RP issuance is suspended, so your result screen can say so.

09

Reading data back

Games can render their own leaderboard and RP display without handling player identity. Reads are scoped to the current run's game.

Leaderboards and player state
// In-game leaderboard, including the player's own row even when
// they are outside the top slice.
const board = await RizGames.leaderboard.get({ limit: 10 });
board.entries.forEach((e) => draw(e.rank, e.displayName, e.score));
if (board.you) drawYou(board.you.rank, board.you.score);

// Live player record, including the RP balance after the last run.
const me = await RizGames.user.get();

10

Errors

Failures reject with a RizError carrying a stable machine-readable code. Branch on the code, never on the message.

Handling failures
try {
  await RizGames.score.submit(score);
} catch (error) {
  // error.code is stable; error.message is not.
  if (error.code === "session_expired") showReconnectScreen();
  else if (error.permanent) reportBug(error);
  // Transient failures never reach here. The SDK already retried them.
}

RizGames.on("session-ended", ({ code }) => pauseGame(code));
RizGames.on("progress", ({ rpBalance }) => updateHud(rpBalance));
not_connected

The game was not launched from its Riz page.

origin_mismatch

The build is served from an origin other than the one registered in Studio.

not_live

The integration has not been approved yet.

invalid_event / invalid_payload

The event name or payload is not accepted. Never retried.

session_inactive / session_expired

The run is over. Every queued event is dropped.

rate_limited

Too many events. The SDK backs off and retries for you.

server_error

A transient Riz failure. The SDK retries up to five times.

capability_disabled

This game is not enabled for that module. Check RizGames.supports() first.

data_limit_exceeded

The save would exceed the per-player quota. Nothing was written.

unknown_error

A failure this SDK build does not recognise. Report it; do not branch on it.

11

Fitting the Riz shell

Your build runs inside a sandboxed iframe under the arcade chrome. These are the exact capabilities it is granted, and the exact things it must not assume.

Granted

Scripts, your own origin storage, forms, pointer lock, popups.

Granted

Autoplay, fullscreen, gamepad, clipboard read and write.

Blocked

alert(), confirm() and prompt(). allow-modals is not granted, so draw your own dialogs.

Blocked

Navigating or reading the top window. Link out with target="_blank".

Blocked

SharedArrayBuffer. The frame is not cross-origin isolated, so ship a single-threaded WASM build.

Test it the way players will see it

Before you submit, load your deployed URL in an iframe on a page you control at 360×640 and at 1920×1080. Anything that breaks there will break on Riz.

12

Submitting your game

Submissions go through Creator Studio. You keep your hosting; Riz only ever loads the URL you register.

Fastest

Community listing

Your deployed game gets a Riz page with likes, ratings, comments and sharing. No SDK, no code changes, and no rankings or RP.

Full ecosystem

Riz Connected

Add the SDK and submit the same deployed URL. Unlocks verified sessions, in-game leaderboards, achievements and server-calculated RP.

What Creator Studio asks for

Title and genre

Shown on your Riz page and in the arcade grid.

Tagline

One line, up to 160 characters. It is the hook on the game card.

Deployed game URL

The exact HTTPS URL Riz will embed. Its origin is locked in as your allowed origin.

Description

At least 20 characters. Include the controls and what a run looks like.

Cover image URL

Optional. Riz generates artwork if you leave it blank.

Accent colour

Themes your game page, loading screen and progress bar.

Languages

Comma separated. Defaults to English.

Mobile tested

Only tick this if the game is genuinely playable with touch alone.

  1. 1Deploy the build to a stable HTTPS URL that allows embedding.
  2. 2In Creator Studio, choose Riz Connected and submit the form above.
  3. 3Copy the public game key from your submission card into the build, then redeploy.
  4. 4Open the game from its Riz page and confirm events land. A creator test session works before approval, but awards no RP.
  5. 5Riz reviews gameplay, embedding, origin, score rules and performance.
  6. 6On approval the Connected badge, rankings and RP go live.

The play URL is fixed once submitted

Riz checks your build's origin against the URL you registered on every single run, so the play URL cannot be edited afterwards. If you need to move hosting, submit the game again at the new URL. Paths and query strings under the same origin are fine to change.

13

Review checklist

Reviewers work through this list. Checking it yourself first is the difference between approved and changes requested.

Served over HTTPS

The Riz page is HTTPS, so an http:// build is blocked as mixed content and will never load.

Embeddable in an iframe

Your host must not send X-Frame-Options: DENY/SAMEORIGIN, and any Content-Security-Policy must allow frame-ancestors https://games.riz.africa. This is the single most common submission failure.

Fills the viewport

The frame is the full window minus a 64px header, and it resizes with the browser and on fullscreen. Do not assume a fixed canvas size; handle resize and portrait.

Stable URL

The exact origin you submit is the one Riz checks on every run. Serving the build from a different origin later fails with origin_mismatch.

Touch input, if you tick mobile

If you mark the game mobile-ready, it must be playable with touch alone, with no keyboard-only controls.

No identity of its own

Third-party cookies and storage are partitioned inside the frame. Use the Riz player id instead of your own login.

For Riz Connected, reviewers additionally check

  • game_started and game_ended fire for every run, including runs the player abandons.
  • Reported scores match what the game actually shows the player.
  • The event rate stays inside the published budget across a full session.
  • Score cannot be inflated by replaying, pausing or reloading mid-run.
  • Failures are handled, so the game does not freeze when an event is rejected.

If changes are requested

Your submission card in Creator Studio shows the reviewer's note and the full review history. Fix the build, redeploy to the same URL, then press “I fixed it. Resubmit for review”. You do not need to submit the game again.

Ready to connect your game?

Submit the deployed URL, choose Riz Connected, and your game key is waiting on the submission card.

Open Creator Studio