Viewer Embed

/

Events

Viewer Events

The viewer reports interactions back to the host page. Subscribe with viewer.on(...) when using the SDK or with a window.addEventListener('message', ...) listener when not.

With the SDK

JS

JavaScript

const viewer = Moshpit.viewer('#moshpit-viewer', {
  publicKey: 'mpk_...',
  sessionEndpoint: '/api/moshpit/viewer-session',
  splatId: 'YOUR_SPLAT_ID',
});
 
const off = viewer.on('viewTracked', ({ viewCount, isNewView }) => {
  console.log('view tracked', { viewCount, isNewView });
});
 
// Unsubscribe later
off();

.on(...) returns an unsubscribe function. Calling viewer.destroy() removes all listeners automatically.

Event reference

EventPayloadFires
ready
undefined

When the first frame has rendered. Anything you call before this fires is queued.

viewTracked
{ viewCount?: number; isNewView?: boolean }

When Moshpit registers a view for analytics. `isNewView` is true the first time a unique visitor opens this splat.

fullscreenChanged
{ fullscreen: boolean }

When the user enters or exits fullscreen.

sessionExpiring
{ expiresAt: string }

About 60 seconds before the session JWT expires. The SDK uses this internally to refresh; intercept it only if you also need to.

qualityChanged
{ quality: "high" | "medium" | "low" }

When the active Viewer quality preset changes. setQuality("auto") reports the resolved default preset, currently "medium".

loaded
{ splatId: string; source: "gallery" | "embed"; hasLods: boolean }

When the Scene finishes loading.

loadError
{ message: string }

When the Scene fails to load.

camera-change
{ position: { x: number; y: number; z: number }; rotation: { yaw: number; pitch: number; roll: number } }

When the embedded Viewer camera pose changes.

scene-change
{ sourceSplatId: string; destinationSplatId: string }

After portal travel loads a different Scene.

cloned
{ splatId: string; sourceSplatId: string }

After the viewer's Remix button successfully copies the scene. `splatId` is the new copy; `sourceSplatId` is the scene that was remixed. The viewer does not navigate — decide what to do with the new scene yourself (open it, refresh a gallery, etc.).

hostFullscreenRequested
{ lockLandscape?: boolean; reason?: "mobile-landscape-interaction" }

When the host page should fullscreen its Viewer container for the embedded mobile experience.

hostFullscreenExitRequested
undefined

When the host page should exit its Viewer-container fullscreen.

error
{ message: string }

Compatibility event emitted alongside `loadError` when the Scene fails to load.

Without the SDK

Listen on window, validate the origin, and check the source discriminator:

JS

JavaScript

const iframe = document.getElementById('moshpit-viewer');
 
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://moshpit.studio') return;
  if (event.source !== iframe.contentWindow) return;
  const data = event.data;
  if (data?.source !== 'moshpit-viewer') return;
 
  switch (data.type) {
    case 'ready':
      // First frame rendered
      break;
    case 'viewTracked':
      console.log(data.payload.viewCount, data.payload.isNewView);
      break;
    case 'fullscreenChanged':
      console.log(data.payload.fullscreen);
      break;
    case 'sessionExpiring':
      // Refresh the session — see Session Tokens
      break;
    case 'cloned':
      console.log('remixed', data.payload.splatId, data.payload.sourceSplatId);
      break;
    case 'loadError':
    case 'error':
      console.error(data.payload.message);
      break;
  }
});

The full message envelope is documented at PostMessage Protocol.

Always validate the sender

Check both the Moshpit event.origin and the iframe event.source. The SDK does this for you; raw addEventListener listeners must do it themselves.

What's next