Developer SDK
Connect your game to scores, leaderboards, pause and ad breaks. There is no library to install: it is a handful of postMessage calls.
Overview
Every game on YoHU.io runs inside an <iframe> on its game page. The page and the game talk with window.postMessage. Each message is a plain object with a source and a type:
- Messages from your game use
source: 'yohu-game'. - Messages from the portal use
source: 'yohu-portal'.
Anything with another source is ignored, so the bridge cannot clash with your own messages. All calls are optional: a game that sends nothing still plays fine, it just will not record scores.
Embedding
The iframe fills the game area and, in full screen or on phones, the whole screen. It is created with these permissions:
<iframe src="your-game/index.html?autoplay=1"
allow="autoplay; fullscreen; gamepad; screen-wake-lock; accelerometer; gyroscope"
allowfullscreen></iframe>- Size your canvas to the iframe (
100vw × 100vh) and handleresizeand orientation changes. Game areas range from about 360 × 450 on phones to full HD. - Do not navigate the top window or open pop-ups. Links to your own site are fine on your credits screen with
target="_blank". - We can host your build (a zip of static files) or embed it from your own
https://URL.
URL flags
For games we host, the portal adds query parameters to your game's URL. Read them with URLSearchParams:
| Flag | When | What your game should do |
|---|---|---|
?autoplay=1 | The player pressed Play on the game page. | Skip the title screen and start a run right away. |
?demo=1&muted=1 | Live preview while the mouse hovers a game card (featured in-house games). | Play an attract loop by itself, keep sound off, send no scores. |
Messages from your game
| type | Fields | What happens |
|---|---|---|
gameover | score (integer, required), best, newBest | The score is saved as the player's personal best. Registered players also enter the leaderboards and the weekly cup. The page shows “New personal best!” when it is one. |
adbreak | none | A natural pause between rounds or levels. See Ad breaks. |
pause / resume | none | Optional status updates when your own pause menu opens or closes. Reserved for future features. |
window.parent.postMessage({ source: 'yohu-game', type: 'gameover', score: Math.floor(score) }, '*');Messages from the portal
| type | Fields | What your game should do |
|---|---|---|
pause | none | Pause. Sent when the player closes the full-screen view on a phone. |
adbreak-ack | none | An ad is about to show. Stay paused and keep sound off. |
adbreak-done | none | Continue the game. |
sound | on (boolean) | Reserved. Mute or unmute if you support it. |
Ad breaks
Post adbreak at natural pauses only: between levels or after a game over, never in the middle of play. The portal decides whether to show an ad (at most every third break, and only when ads are switched on) and answers:
- No ad: it replies
adbreak-donestraight away. - Ad: it replies
adbreak-ack, shows a short ad over the game, then sendsadbreak-done.
Wait about 400 ms for an answer. If nothing arrives (for example when your game runs on its own site), carry on. Never wait longer than 20 seconds in total.
Scores & leaderboards
- Send one
gameoverper finished run, with the final score as a whole number from 0 to 1,000,000,000. Do not send scores mid-run. - Up to 12 scores per minute are accepted from a player; extra ones are dropped.
- Guests get personal bests on their device. When they log in, those scores move to their account.
- Leaderboard pages and weekly cups currently feature in-house Originals. Partner games keep personal bests and can join the boards on request.
Example bridge
Drop this into any engine. It works the same when your game runs on its own, outside the portal.
const YoHU = {
embedded: window.parent !== window,
post(type, data = {}) {
if (this.embedded) window.parent.postMessage({ source: 'yohu-game', type, ...data }, '*');
},
gameOver(score) { this.post('gameover', { score: Math.max(0, Math.floor(score)) }); },
adBreak(next) {
if (!this.embedded) return next();
let acked = false, done = false;
const finish = () => { if (done) return; done = true; removeEventListener('message', onMsg); next(); };
const onMsg = (e) => {
const d = e.data;
if (!d || d.source !== 'yohu-portal') return;
if (d.type === 'adbreak-ack') acked = true;
if (d.type === 'adbreak-done') finish();
};
addEventListener('message', onMsg);
this.post('adbreak');
setTimeout(() => { if (!acked) finish(); }, 400); // no answer: carry on
setTimeout(finish, 20000); // safety net
},
};
// The portal asks the game to pause
addEventListener('message', (e) => {
if (e.data?.source === 'yohu-portal' && e.data.type === 'pause') game.pause();
});
// Start straight away after the player pressed Play
if (new URLSearchParams(location.search).has('autoplay')) game.start();
// When a run ends, and between levels
YoHU.gameOver(score);
YoHU.adBreak(() => game.nextLevel());Testing checklist
- Plays inside an iframe at 360 × 640 (phone, portrait) and 1920 × 1080 (desktop).
- Touch and keyboard or mouse both work; nothing needs a right click on phones.
- Pauses on
visibilitychangeand when it receivespause. - Sound starts only after the first tap or key press (browser autoplay rules).
gameoveris posted exactly once per run, with a whole number.- No errors in the browser console, no requests to unexpected domains.
Ready? Submit your game and mention in the notes that it uses the bridge.