<!-- Generated from /docs/postmessage. Do not edit by hand. -->
> Source: https://inspekt.gg/docs/postmessage · Control the embedded 3D skin viewer from your page: every postMessage event and command, and how the frame pins messages to your origin.

# postMessage API

Use [the loader](#using-the-loader) unless you drive a hand-built `<iframe>`. Its `handle.on()` and `handle.set…()` methods build every message on this page for you.

## The envelope

Every message, in either direction, is a plain object with `channel: "cs2embed"` and a `type`. Both sides ignore any other message on the window, such as one from a browser extension or an ad script.

### Origin pinning

Both directions are pinned to an origin:

- **Outbound (frame → host)** messages go to the `origin` parameter on the embed URL, which the loader sets. Without it they go to your page's `document.referrer`, and with neither to `*`. They carry the item's id, wear, seed and name tag, timings, load progress, refusal and fallback codes, and screenshot images.
- **Inbound (host → frame)** messages are accepted only from `window.parent`, at the exact origin resolved above.
- **The `origin` parameter** must be a bare `http` or `https` origin of at most 255 characters. A trailing slash is trimmed. A value with a path, a query string or another scheme is ignored, and messages fall back to `*`.

## Frame → host: events

Subscribe with `handle.on(type, fn)`. Without the loader, listen for `message` events on `window`.

| Type | Payload | When |
| --- | --- | --- |
| ready | `{ backend }` | The frame has started and accepts commands. The loader queues anything you send earlier and sends it when this fires. |
| loaded | `{ id, name, ms, notes }` | The item is on screen. Usually this is the opening still ([first paint](https://inspekt.gg/docs/embed#first-paint)), about a second in, before the 3D view has drawn. It also fires alongside a `fallback`, because the still stays visible under the fallback's note. `ms` counts from the frame's start. `notes` lists non-fatal decode notes and is usually empty. |
| interactive | `{ id, ms, notes }` | The 3D view has drawn its first frame. The visitor can now orbit, and `screenshot` returns the live render. The loader queues commands sent before this. `notes` is the complete list, including notes from applying the inspect link's stickers and charm. Notes number sticker slots from 0, as `st` does. |
| error | `{ code, message }` | An **access** refusal, such as an unknown key, an unregistered origin or a spent quota. Fires instead of loading the frame. See [Error codes](https://inspekt.gg/docs/errors). |
| fallback | `{ code, message, id }` | An **item** problem after access was granted: missing WebGPU support, a bad inspect link, an item outside the catalog, or a load failure. The frame keeps its still and shows the reason in a note over it. A frame without a still shows a 2D card instead. See [fallback codes](https://inspekt.gg/docs/errors#fallback-codes). |
| progress | `{ value, label }` | Load progress, `value` from `0` to `1`. |
| state | `{ id, wear, seed, nametag }` | The current item state, sent after each change, including changes from your own `setItem` commands. |
| screenshot | `{ requestId, blob, error? }` | The answer to a `screenshot` command, matched by `requestId`. `blob` is `null` and `error` is set on failure. The loader turns this into the `Promise` that `handle.screenshot()` returns. |

## Host → frame: commands

Send with the matching `handle.set…()` / `handle.resetView()` / `handle.screenshot()` method, or post the object yourself.

| Type | Payload | Effect |
| --- | --- | --- |
| setItem | `{ inspect?, s?, wear?, seed?, st?, name? }` | Swap the item without remounting the frame. Takes the same fields and rules as the embed parameters. |
| setScene | `{ scene }` | Change the backdrop. |
| setView | `{ view }` | `front` \| `back` \| `top` \| `hero`. |
| setAutorotate | `{ on }` | Turn autorotate on or off. |
| setQuality | `{ renderScale }` | Set the internal render resolution. |
| resetView | `{}` | Return the camera to its default framing. |
| screenshot | `{ requestId, width?, height?, format?, quality? }` | Capture the current frame as a blob, answered by a `screenshot` event with the same `requestId`. |

## With the loader

```javascript
var viewer = CS2Embed.mount(el, { inspect: inspectLink });

viewer.on('loaded', function (e) { console.log(e.id, e.ms + ' ms'); });
viewer.on('error', function (e) { console.error('access refused:', e.code, e.message); });
viewer.on('fallback', function (e) { console.warn('showing fallback:', e.code); });

viewer.setView('back');
viewer.setAutorotate(false);

viewer.screenshot({ width: 1200, height: 630, format: 'image/webp', quality: 0.9 })
  .then(function (blob) { /* a Blob, ready to upload or download */ })
  .catch(function (err) { console.error(err); });

viewer.destroy(); // unmounts the iframe and drops all pending listeners
```

## With a raw iframe

```javascript
var EMBED_ORIGIN = 'https://inspekt.gg';
var frame = document.querySelector('iframe.cs2-viewer');

// send commands once the frame is ready
window.addEventListener('message', function (e) {
  if (e.origin !== EMBED_ORIGIN) return;
  var d = e.data;
  if (!d || d.channel !== 'cs2embed') return;

  if (d.type === 'ready') {
    frame.contentWindow.postMessage(
      { channel: 'cs2embed', type: 'setView', view: 'back' },
      EMBED_ORIGIN
    );
  }
  if (d.type === 'error') {
    console.error('embed refused:', d.code, d.message);
  }
});
```

Add `origin=https://yoursite.example` to the iframe's `src` so the frame addresses its messages to your page. See [Embed parameters](https://inspekt.gg/docs/embed).
