Skip to content

Viewer messaging API

The Maverick Excelsior Viewer is a lightweight, embeddable 3D renderer built for the web: it runs in the browser, with nothing to install, on every device from phones to workstations. It loads .webex scenes and renders them in real time with physically-based materials. This page is the reference for driving an embedded viewer from JavaScript.

When to use the SDK

You do not need any of this just to publish a model. The simple route is the Excelsior Editor's Create Link button: one click uploads the scene and gives you a shareable, embeddable viewer page (see Sharing a viewer online).

Reach for the Viewer SDK when you want custom behavior: your page driving the viewer, or the viewer driving your page. Build your own configurator, run the viewer directly against your model catalog, update prices as the customer switches materials and stones, restyle the controls to match your brand, or feed captures into your own systems.

As a proof of concept, catalog.maverickexcelsior.com is a mockup webshop built exactly this way: every product page embeds the viewer over a model catalog, with materials, stone types, and prices wired into the page.

Mockup webshop built on the Viewer SDK

Communication model

JavaScript and the viewer talk over two one-way string channels:

Direction Channel Description
Page to viewer wasm_i(op, d0, d1, d2) Send a command
Viewer to page CustomEvent('wasm_o') with detail = { op, d0, d1, d2 } Receive an event

All four parameters are strings. Trailing empty strings may be omitted.

Warning

If you have older integration code that calls window.webex_in(...), migrate it to the wasm_i interface documented here.

Wiring the channels

// Call once after Module.onRuntimeInitialized fires.
// The stock webex-viewer-module.js already defines this for you.
window.wasm_i = (op, d0, d1, d2) => {
  Module.ccall('wasm_i', null,
    ['string', 'string', 'string', 'string'],
    [op, d0 || '', d1 || '', d2 || '']);
};

// The viewer dispatches its events itself as CustomEvent('wasm_o')
// on window; just listen for them.
window.addEventListener('wasm_o', (e) => {
  const { op, d0, d1, d2 } = e.detail;
  // Handle events...
});

Authentication

api_key

Must be set before loading any scene; it encodes the domain allowlist that open_scene URLs are checked against. Provide it at startup via Module.json.api_key or send it explicitly:

wasm_i('api_key', 'ak_live_xxxxxxxxxxxxxxxx');

Startup values (Module.json)

Instead of sending them after startup, any of these can be provided as string fields of Module.json and are applied automatically during boot, in this order: set_quality, set_clear_color, set_infotag, set_autospin, api_key, and open_scene.

var Module = {
  canvas: document.getElementById('webex-canvas'),
  json: {
    api_key: 'ak_live_xxxxxxxxxxxxxxxx',
    set_quality: 'high',
    open_scene: 'https://assets.example.com/ring.webex'
  }
};

Scene lifecycle

open_scene

wasm_i('open_scene', 'https://assets.example.com/ring.webex');

The URL is checked against the API key's domain allowlist; a viewer showing content that is not covered by its key runs in unlicensed (watermarked) mode. Repeated calls with the same URL are ignored.

Where should the scene live? In the SDK, the scene is a .webex file you export from the Excelsior Editor (Export Interactive Scene) and host yourself. The simplest home is right next to your page, on the same domain: a relative path such as ./scenes/ring.webex just works, with nothing to configure, and it is what all the working examples do. You can also serve scenes from a separate assets host or CDN of yours; in that case the browser's cross-origin rules kick in (see the note below).

One route that is not for the SDK: the share links and shortcodes produced by the editor's Create Link button. Those belong to our hosted viewer and to iframe embeds, and the files behind them are served with a cross-origin policy that only admits our own pages. Point your SDK page at your own hosting instead.

Event d0 Meaning
open_scene_starting filename Loading has begun.
open_scene_progress percentage Download progress, 0 to 100.
open_scene_ready Scene decoded; warmup begins.
open_scene_complete Scene fully loaded and interactive.

CORS is the number one show-stopper

open_scene fetches the scene with a normal browser request, so the browser's cross-origin rules apply. Same origin as your page: always fine. A different origin (your assets subdomain, a bucket, a CDN): that server must answer with an Access-Control-Allow-Origin header that covers your page's domain, or the browser will silently refuse the download. S3 and R2 buckets do not send this header until you configure it. If a scene loads when hosted next to your page but not from your CDN, this is almost always why.

close_scene

wasm_i('close_scene');

Unloads the current scene and returns the viewer to its empty state.

Scene controls

Command d0 Description
set_quality medium / high / ultra Quality preset. medium: base geometry, no effects. high: better geometry plus reflections. ultra: best geometry, ambient occlusion, reflections, and glare.
set_clear_color black, white, #RRGGBB, or RRGGBB Background clear color behind the scene.
set_autospin 0 / 1 Continuous automatic rotation.
set_infotag 0 / 1 Information label overlay.
zoom integer delta Camera zoom. Positive zooms in, negative zooms out.
capture Asynchronously capture the current frame. The reply arrives as a capture_data event whose d0 is a base64 JPEG data URI (max 512 px, quality 85). Intended for thumbnails.

Materials

apply_mtl

wasm_i('apply_mtl', 'Metal 01', 'White Gold 18k');
Parameter Description
d0 Layer name, matched case-sensitively. Every layer with that exact alias receives the material.
d1 Material name from the library, matched case-sensitively. See Content library names.

The reply is an applied event (d0 = material, d1 = layer, d2 = material) on success, or a failed event with the same payload when the layer or material name does not match anything.

Properties

set_string and get_string

wasm_i('set_string', '::globals', 'globals_ibl_angle', '45.0');
wasm_i('get_string', '::camera', 'camera_hfov');
Parameter Description
d0 Node: ::globals, ::camera (alias ::main_camera), ::render_pipeline (alias ::main_pipeline), ::animation, or a layer name (matched at any depth).
d1 Property USTR name, e.g., globals_resolution_w. See Scene properties (USTR).
d2 (set only) New value as a string.

get_string replies with a get_string event carrying d0 = node, d1 = ustr, d2 = current value. After a set, the render restarts to reflect the change.

The live camera can be driven through the virtual properties on ::camera (tracker_cam_theta, tracker_cam_phi, tracker_cam_dolly, tracker_cam_pan_x, tracker_cam_pan_y, tracker_cam_hfov), which read and write the on-screen camera directly.

Message reference

Commands (page to viewer)

Command d0 d1 d2 Description
api_key key Authenticate; sets the scene domain allowlist.
open_scene URL Load a .webex scene.
close_scene Unload the current scene.
set_quality preset medium / high / ultra.
set_clear_color color Background color.
set_autospin 0/1 Auto rotation.
set_infotag 0/1 Info label.
zoom delta Camera zoom.
capture Frame snapshot.
apply_mtl layer material Apply a library material to a layer.
set_string node ustr value Set a property.
get_string node ustr Query a property.

Events (viewer to page)

Event d0 d1 d2 Description
open_scene_starting filename Loading began.
open_scene_progress percentage Download progress.
open_scene_ready Scene decoded, warming up.
open_scene_complete Scene interactive.
get_string node ustr value Property query reply.
applied material layer material apply_mtl succeeded.
failed material layer material apply_mtl matched no layer or material.
capture_data data URI Base64 JPEG of the current frame.

Working examples

A set of small, view-source-friendly pages exercises everything on this page against the live viewer. Each one is dissected, with its source annotated, on its own page:

Minimal integration example

The page below loads its scene from a separate assets host. Remember the main show-stopper: that host must reply with CORS headers that cover your page's domain, or simply host the scene next to the page and use a relative path (see open_scene).

<!DOCTYPE html>
<html>
<head><title>WebEx Viewer</title></head>
<body>
  <canvas id="webex-canvas" style="width:100%;height:100vh;"></canvas>
  <script>
    var Module = {
      canvas: document.getElementById('webex-canvas'),
      json: { api_key: 'ak_live_xxxxxxxxxxxxxxxx' }
    };

    Module.onRuntimeInitialized = () => {
      window.wasm_i = (op, d0, d1, d2) => {
        Module.ccall('wasm_i', null,
          ['string', 'string', 'string', 'string'],
          [op, d0 || '', d1 || '', d2 || '']);
      };

      window.addEventListener('wasm_o', (e) => {
        if (e.detail.op === 'open_scene_complete') {
          wasm_i('apply_mtl', 'Metal 01', 'White Gold 18k');
          wasm_i('set_quality', 'ultra');
          wasm_i('set_clear_color', '#F0F0F0');
          wasm_i('set_autospin', '1');
          wasm_i('set_string', '::globals', 'globals_ibl_angle', '45.0');
        }
      });

      wasm_i('open_scene', 'https://assets.example.com/ring.webex');
    };
  </script>
  <script src="webex-viewer.js"></script>
</body>
</html>

Found a mistake?

We do our best to keep this reference accurate and in step with the product, but mistakes and omissions can slip through, and they are never intentional. If anything here looks wrong, incomplete, or unclear, please contact us and we will gladly fix it.