Y8 SDK 2.x: initialisation, showAd() breaks and rewarded ads

Integrate the Y8 JavaScript SDK: async loading and the y8sdk.ready race, init with app and game IDs, interstitial and rewarded showAd() calls, and local testing.

Published
Sources checked
Versions
Y8 SDK 2.x (minimal-sdk/2-0), docs as of 2026-10-01, Phaser 3.90.0

Y8 hosts web games and shares in-game ad revenue with developers whose games integrate its SDK (see the Y8 page for the published terms). The JavaScript SDK covers sign-in, ads, cloud storage, achievements and leaderboards. This guide covers the parts a typical arcade game needs first: loading, initialisation and ads, using the same example game and adapter interface as the other SDK guides. Documentation checked on 2026-10-01.

Load the SDK and win the ready race

The getting-started page loads the SDK asynchronously in <head>:

<script src="https://cdn.y8.com/minimal-sdk/2-0/y8.min.js" async></script>

Because the script is async, it may finish before or after your game code runs. The SDK signals readiness with a y8sdk.ready event on window; after that, y8.sdk() returns the SDK object. If the script won the race, the event has already fired and your listener never runs. Y8’s fix is to call window.y8.emitReadyEvent() right after adding the listener, which makes the SDK announce itself again. The docs call this race the most common reason for an SDK that “does nothing”, and note that it can work every time locally and still fail in production.

Initialise with your IDs

Both IDs come from your game’s SDK Initialization tab in the Y8 Developer Portal:

  • the App ID identifies the application and goes in the first argument, appConfig (with autoLogin);
  • the Game ID is needed for ads and goes in the optional second argument, adConfig, together with preloadAdBreaks, sound and onReady.

Initialise once, at startup. If your game shows no ads you can leave out adConfig.

Ads: pause in the callback, not before the call

The advertising page opens with the rule that matters most: a break does not always show an ad. When none is available, or the previous ad was too recent, the SDK skips the break without calling beforeAd or afterAd. So pause and mute inside beforeAd, resume inside afterAd. If you pause before calling showAd(), a skipped break leaves the game frozen.

An interstitial is one call. Every field is optional; type defaults to start and name is a label that appears in the Y8 dashboard:

y8Sdk.showAd({
  type: 'next',
  name: 'level-complete',
  beforeAd: () => pauseGame(),
  afterAd: () => resumeGame(),
  adBreakDone: (info) => console.log(info.breakStatus),
}).catch((error) => console.error(error));

Placements describe what the player is doing: start (loaded, before play), pause, next (between levels), browse (menus) and reward. Two warnings from the same page:

  • Never request preroll. Y8 runs the preroll itself before your UI appears.
  • The method is showAd, not adBreak. The object looks like Google’s Ad Placement API, but adBreak does not exist on the Y8 SDK and fails silently.

Rewarded ads

A rewarded break uses type: 'reward' and three extra callbacks:

  • beforeReward(showAdFn) runs only when a rewarded ad is ready. Show your own “watch an ad for a reward?” prompt there and call showAdFn() if the player accepts. Nothing plays until you do.
  • adViewed runs when the player watched to the end. Grant the reward here, not in beforeReward.
  • adDismissed runs when they closed it early. Grant nothing.

The useful property of this design is that you never show a reward button for an ad that does not exist: the prompt appears only from beforeReward.

The adapter

The adapter waits for the SDK (with a timeout, so a blocked script cannot stall the game), initialises it, and maps the example game’s breaks onto showAd():

// file: examples/lib/adapters/y8.js
// Y8 SDK 2.x adapter (JavaScript).
// Docs: https://docs.y8.com/sdk/intro/ , https://docs.y8.com/sdk/advertising/ ,
//       https://docs.y8.com/sdk/local-development/ (checked 2026-10-01)
// index.html loads the SDK asynchronously in <head>:
//   <script src="https://cdn.y8.com/minimal-sdk/2-0/y8.min.js" async></script>
import { gameplayGuard } from './types.js';
import { createNoopAdapter } from './noop.js';

/**
 * Waits for the "y8sdk.ready" event, covering the documented race where the async script loaded first.
 * Resolves with y8.sdk(), or null after `timeoutMs` (script blocked or offline) so the game still starts.
 * @param {any} win
 * @param {number} timeoutMs
 * @returns {Promise<any>}
 */
export function waitForY8(win, timeoutMs) {
  return new Promise((resolve) => {
    const timer = setTimeout(() => resolve(null), timeoutMs);
    win.addEventListener(
      'y8sdk.ready',
      () => {
        clearTimeout(timer);
        resolve(win.y8.sdk());
      },
      { once: true },
    );
    // Per Y8's docs: ask the SDK to announce itself again in case the event already fired.
    if (win.y8 && win.y8.emitReadyEvent) win.y8.emitReadyEvent();
  });
}

/**
 * @param {{ appId: string, gameId: string, win?: any, timeoutMs?: number }} options
 * @returns {Promise<import('./types.js').PortalAdapter>}
 */
export async function createY8Adapter({ appId, gameId, win = globalThis, timeoutMs = 8000 }) {
  const y8Sdk = await waitForY8(win, timeoutMs);
  if (!y8Sdk) return createNoopAdapter('Y8 SDK did not load');
  // gameId is required for ads; preload so the first break has an ad ready.
  y8Sdk.init({ appId, autoLogin: true }, { gameId, preloadAdBreaks: 'on', sound: 'on', onReady: () => {} });

  const guard = gameplayGuard(
    () => {},
    () => {},
  );

  return {
    name: 'y8',
    loadingStart() {},
    loadingStop() {},
    ...guard,
    commercialBreak(hooks) {
      guard.gameplayStop();
      return new Promise((resolve) => {
        y8Sdk
          .showAd({
            // Y8 never accepts 'preroll' from games (the platform runs it); 'next' between levels.
            type: hooks.placement ?? 'next',
            name: hooks.name,
            // Pause inside beforeAd, not before the call: skipped breaks fire no callbacks.
            beforeAd: () => hooks.pause(),
            afterAd: () => hooks.resume(),
            adBreakDone: () => resolve(undefined),
          })
          .catch(() => resolve(undefined));
      });
    },
    rewardedBreak(hooks) {
      guard.gameplayStop();
      return new Promise((resolve) => {
        let settled = false;
        let rewarded = false;
        const finish = (/** @type {boolean} */ value) => {
          if (!settled) {
            settled = true;
            resolve(value);
          }
        };
        y8Sdk
          .showAd({
            type: 'reward',
            name: hooks.name,
            beforeAd: () => hooks.pause(),
            afterAd: () => hooks.resume(),
            // Only runs when a rewarded ad is ready; nothing plays unless showAdFn() is called.
            beforeReward: (/** @type {() => void} */ showAdFn) =>
              hooks.showPrompt(
                () => showAdFn(),
                () => finish(false),
              ),
            adViewed: () => {
              rewarded = true; // watched to the end: grant
            },
            adDismissed: () => {
              rewarded = false; // closed early: grant nothing
            },
            adBreakDone: () => finish(rewarded),
          })
          .catch(() => finish(false));
      });
    },
    onMuteRequest() {},
  };
}

The eight-second timeout is our choice, not a Y8 value: long enough for a slow connection, short enough that a player with an ad blocker still gets the game. Adjust it to your loading time.

The vanilla entry point waits for the adapter before starting the game:

// file: examples/vanilla-y8/main.js
// Y8 SDK example, vanilla JS. Build: npm run examples:build, then serve examples/dist/ on localhost.
import { createY8Adapter } from '../lib/adapters/y8.js';
import { startTapGame } from '../lib/tap-game.js';

createY8Adapter({ appId: 'YOUR_Y8_APP_ID', gameId: 'YOUR_Y8_GAME_ID' }).then((adapter) => {
  adapter.loadingStart();
  // Load your assets here. This example has none, so loading ends immediately.
  adapter.loadingStop();
  startTapGame({
    canvas: /** @type {HTMLCanvasElement} */ (document.getElementById('game')),
    overlay: /** @type {HTMLElement} */ (document.getElementById('overlay')),
    adapter,
  });
  console.info(`[example] portal adapter: ${adapter.name}`);
});

And the Phaser 3.90.0 build, with the same scenes as every other portal example:

// file: examples/phaser-y8/main.js
// Y8 SDK example, Phaser 3. Build: npm run examples:build, then serve examples/dist/ on localhost.
import { createY8Adapter } from '../lib/adapters/y8.js';
import { createPhaserGame } from '../lib/phaser-game.js';

createY8Adapter({ appId: 'YOUR_Y8_APP_ID', gameId: 'YOUR_Y8_GAME_ID' }).then((adapter) => {
  createPhaserGame(adapter, 'game');
  console.info(`[example] portal adapter: ${adapter.name}`);
});

Testing locally

The local development page has three practical points:

  1. Serve the game over HTTP (for example python3 -m http.server 8000) and open http://localhost:8000. Opening index.html as a file:// URL gives a page with no working SDK, and the symptoms look like a broken integration.
  2. Sign-in works on localhost without setup. Other origins (a phone on your network, a tunnel) must be added under the game’s SDK Initialization tab first.
  3. Automatic sign-in only works on the game’s Y8 page. Locally the game starts anonymous; call the login method from a click to test the signed-in path.

Review and the readiness checker

Y8’s FAQ says every game is reviewed before publishing and that you must complete the SDK requirements the Developer Portal shows for your game. Run the Portal Readiness Checker on your zip first: it recognises the Y8 SDK tag and the y8sdk.ready and y8.sdk() calls, and warns about other portals’ SDKs left in the build.

What to check before you submit

  • The SDK tag is in <head> with async, and your code keeps the emitReadyEvent() fallback.
  • init() runs once with the App ID, and with adConfig including the Game ID if you show ads.
  • Every break pauses in beforeAd and resumes in afterAd; nothing is paused before showAd().
  • No preroll placement and no adBreak calls.
  • Reward prompts appear only from beforeReward; rewards are granted only in adViewed.
  • The game starts and plays when the SDK never loads.
  • You tested over http://localhost, not file://.

Limits of this guide

This guide covers loading, initialisation and video ads in the JavaScript SDK. It does not cover banners (which need per-game approval from Y8), authentication flows, cloud storage, achievements, leaderboards, or the Unity, Construct 3 and Haxe wrappers. The adapter was unit-tested against a mock that follows the documented callback order; it has not been run against Y8’s live SDK.

Questions

Why does my Y8 SDK integration do nothing, with no error?
Y8's docs name the most common cause: the SDK script is async, so it can finish loading before your y8sdk.ready listener exists. Keep the documented emitReadyEvent() call after adding the listener so the SDK announces itself again.
Can I request a preroll from the Y8 SDK?
No. Y8 runs the preroll itself before your game appears, and its docs say requesting it from the SDK is not supported. Use start, pause, next, browse or reward.
Is it adBreak() or showAd() on Y8?
showAd(). The placement object looks like Google's Ad Placement API, but Y8's docs warn that adBreak does not exist on the Y8 SDK and calling it fails silently.

Sources

  1. Y8 docs: Getting started (SDK)docs.y8.com, accessed
  2. Y8 docs: Advertisingdocs.y8.com, accessed
  3. Y8 docs: Local developmentdocs.y8.com, accessed
  4. Y8 Developer Portal: FAQdeveloper.y8.com, accessed