Configurator – Two-Way Communication via postMessage

Introduction

To enable secure communication between the Configurator and the host website, embedding must be authorized with a session token and initialized using a postMessage handshake.

Setup:

  1. Open the E-commerce Settings tab in the Alter Product app.
  2. In the Integration section, set the domain where the iframe will be embedded (e.g. https://yourwebsite.com or http://localhost:3000).

Flow:

  1. Your website loads the Configurator iframe.
  2. The iframe and your website perform a postMessage handshake (nonce).
  3. The iframe requests a session token from your website (ALTER_TOOL_INIT_SESSION).
  4. Your backend requests the token from Alter Product API and your website responds with ALTER_TOOL_SESSION_READY.
  5. Now you can send Configurator commands (e.g. ALTER_CONFIGURATOR_GET_PRODUCT_DATA) and receive events (e.g. ALTER_CONFIGURATOR_DATA_RESPONSE).

You can also add metadata keys to each variant in the design — in the Product Configuration tab. These keys will be included in the Configurator payload.

Messages sent by the Configurator

TypeDescription
ALTER_CHILD_HELLOHandshake init (contains a nonce)
ALTER_TOOL_INIT_SESSIONRequests a session token from the host website
ALTER_TOOL_READYConfigurator is authorized and ready
ALTER_CONFIGURATOR_ADD_TO_CARTUser clicked “Add to Cart”
ALTER_CONFIGURATOR_DATA_RESPONSEResponse to product data request
ALTER_TOOL_ERRORFatal / authorization / runtime error

Messages received by the Configurator

TypeDescription
ALTER_PARENT_ACKHandshake confirmation (must include the nonce)
ALTER_TOOL_SESSION_READYProvides session token to authorize the Configurator
ALTER_TOOL_SESSION_ERRORToken request failed (host-side error)
ALTER_CONFIGURATOR_GET_PRODUCT_DATARequests configured product data from the Configurator

Full Example (HTML + JS)

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8" />
5  <title>Alter Product Configurator Communication (Handshake + Token + postMessage)</title>
6  <meta name="viewport" content="width=device-width, initial-scale=1" />
7  <style>
8    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
9    iframe { width: 100%; height: 600px; border: 0; display: block; border-radius: 12px; }
10    .row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
11    button { padding: 10px 14px; border-radius: 10px; border: 1px solid #ddd; background: #fff; cursor: pointer; }
12    button:disabled { opacity: 0.5; cursor: not-allowed; }
13    pre { background: #0b1020; color: #d7e1ff; padding: 12px; border-radius: 12px; overflow:auto; }
14  </style>
15</head>
16<body>
17  <h1>Configurator — Two-Way Communication via postMessage</h1>
18
19  <div class="row">
20    <button id="getDataBtn" disabled>Get Configured Product</button>
21  </div>
22
23  <iframe
24    id="configuratorWidget"
25    src="https://alterproduct.com/app/configurator/1?nav=0&add_to_cart=1"
26    title="Alter Product Configurator"
27    allowfullscreen>
28  </iframe>
29
30  <h3>Logs</h3>
31  <pre id="log"></pre>
32
33  <script>
34    const IFRAME_ORIGIN = 'https://alterproduct.com';
35
36    const iframe = document.getElementById('configuratorWidget');
37    const getDataBtn = document.getElementById('getDataBtn');
38    const logEl = document.getElementById('log');
39
40    let handshakeOk = false;
41    let toolReady = false;
42
43    function log(...args) {
44      const line = args.map(a => (typeof a === 'string' ? a : JSON.stringify(a, null, 2))).join(' ');
45      logEl.textContent += line + '\n';
46    }
47
48    function postToIframe(payload) {
49      iframe.contentWindow.postMessage(payload, IFRAME_ORIGIN);
50    }
51
52    /**
53     * Your backend MUST request a session token (recommended).
54     * This endpoint is owned by YOU and should call Alter Product API using server-side credentials.
55     * Expected response: { sessionToken: "..." }
56     */
57    async function getSessionTokenFromYourBackend({ tool, mode, uiDesignId }) {
58      const url = new URL('/api/alter/session-token', window.location.origin);
59      url.searchParams.set('tool', tool);           // viewer | configurator | customizer
60      url.searchParams.set('mode', mode || 'design');
61      url.searchParams.set('uiDesignId', String(uiDesignId || 0));
62
63      const res = await fetch(url.toString(), { method: 'GET' });
64      if (!res.ok) throw new Error('Failed to get session token');
65      const data = await res.json();
66      if (!data || !data.sessionToken) throw new Error('Missing sessionToken');
67      return data.sessionToken;
68    }
69
70    // UI: request configured product data (only after tool is ready)
71    getDataBtn.addEventListener('click', () => {
72      if (!toolReady) return;
73      log('[Host] -> Configurator: ALTER_CONFIGURATOR_GET_PRODUCT_DATA');
74      postToIframe({ type: 'ALTER_CONFIGURATOR_GET_PRODUCT_DATA' });
75    });
76
77    window.addEventListener('message', async (event) => {
78      // 1) Validate origin
79      if (event.origin !== IFRAME_ORIGIN) return;
80
81      // 2) Validate source (must be the embedded iframe)
82      if (event.source !== iframe.contentWindow) return;
83
84      const msg = event.data || {};
85      if (!msg.type || typeof msg.type !== 'string') return;
86
87      // -----------------------
88      // A) HANDSHAKE
89      // Configurator -> Host: ALTER_CHILD_HELLO { nonce }
90      // Host         -> Configurator: ALTER_PARENT_ACK { nonce }
91      // -----------------------
92      if (msg.type === 'ALTER_CHILD_HELLO') {
93        const nonce = msg.nonce;
94        if (!nonce || typeof nonce !== 'string') return;
95
96        handshakeOk = true;
97        log('[Configurator] -> Host: ALTER_CHILD_HELLO', { nonce });
98
99        log('[Host] -> Configurator: ALTER_PARENT_ACK');
100        postToIframe({ type: 'ALTER_PARENT_ACK', nonce });
101
102        return;
103      }
104
105      // Ignore everything until handshake is done
106      if (!handshakeOk) return;
107
108      // -----------------------
109      // B) TOKEN INIT (on-demand)
110      // Configurator -> Host: ALTER_TOOL_INIT_SESSION { payload: { tool, mode, uiDesignId } }
111      // Host         -> Configurator: ALTER_TOOL_SESSION_READY { token }
112      // -----------------------
113      if (msg.type === 'ALTER_TOOL_INIT_SESSION') {
114        try {
115          const payload = msg.payload || {};
116          const tool = String(payload.tool || 'configurator').toLowerCase();
117          const mode = String(payload.mode || 'design');
118          const uiDesignId = Number(payload.uiDesignId || 0);
119
120          log('[Configurator] -> Host: ALTER_TOOL_INIT_SESSION', { tool, mode, uiDesignId });
121
122          const token = await getSessionTokenFromYourBackend({ tool, mode, uiDesignId });
123
124          log('[Host] -> Configurator: ALTER_TOOL_SESSION_READY');
125          postToIframe({ type: 'ALTER_TOOL_SESSION_READY', token });
126        } catch (e) {
127          log('[Host] -> Configurator: ALTER_TOOL_SESSION_ERROR', String(e && e.message ? e.message : e));
128          postToIframe({ type: 'ALTER_TOOL_SESSION_ERROR', error: String(e && e.message ? e.message : e) });
129        }
130        return;
131      }
132
133      // -----------------------
134      // C) Configurator lifecycle & events
135      // -----------------------
136      if (msg.type === 'ALTER_TOOL_READY') {
137        toolReady = true;
138        getDataBtn.disabled = false;
139        log('[Configurator] -> Host: ALTER_TOOL_READY', msg);
140        return;
141      }
142
143      if (msg.type === 'ALTER_CONFIGURATOR_DATA_RESPONSE') {
144        log('[Configurator] -> Host: ALTER_CONFIGURATOR_DATA_RESPONSE', msg.payload);
145        return;
146      }
147
148      if (msg.type === 'ALTER_CONFIGURATOR_ADD_TO_CART') {
149        log('[Configurator] -> Host: ALTER_CONFIGURATOR_ADD_TO_CART', msg.payload);
150        return;
151      }
152
153      if (msg.type === 'ALTER_TOOL_ERROR') {
154        log('[Configurator] -> Host: ALTER_TOOL_ERROR', msg);
155        return;
156      }
157    });
158  </script>
159</body>
160</html>

Example payload

{
  "userDesignId": 10,
  "designName": "Mug 450ml (15oz)",
  "totalPrice": {
    "value": 0,
    "currency": "EUR"
  },
  "productGroup": {
    "id": 1,
    "name": {
      "pl": "Kubek 450ml (15oz)",
      "en": "Mug 450ml (15oz)"
    }
  },
  "productItems": [
    {
      "model3d": {
        "id": 3
      },
      "size": {
        "id": 9,
        "name": {
          "pl": "450ml (15oz)",
          "en": "450ml (15oz)"
        },
        "measureSize": {
          "D": 8.65,
          "H": 11.95
        },
        "externalMapping": {
          "attribute": {
            "internalId": 123,
            "slug": "pa_size",
            "label": "Size"
          },
          "term": {
            "internalId": 456,
            "slug": "450ml-15oz",
            "label": "450ml (15oz)"
          }
        }
      },
      "material": {
        "id": 3,
        "name": {
          "pl": "Ceramika",
          "en": "Ceramic"
        },
        "externalMapping": {
          "attribute": {
            "internalId": 124,
            "slug": "pa_material",
            "label": "Material"
          },
          "term": {
            "internalId": 457,
            "slug": "ceramic",
            "label": "Ceramic"
          }
        }
      },
      "printMethod": {
        "id": 5,
        "name": {
          "pl": "Sublimacja",
          "en": "Sublimation"
        },
        "externalMapping": {
          "attribute": {
            "internalId": 125,
            "slug": "pa_printing-method",
            "label": "Printing method"
          },
          "term": {
            "internalId": 458,
            "slug": "sublimation",
            "label": "Sublimation"
          }
        }
      },
      "color": {
        "id": 11,
        "name": {
          "pl": "Domyślny",
          "en": "Default"
        },
        "hex": "#FFFFFF",
        "externalMapping": {
          "attribute": {
            "internalId": 126,
            "slug": "pa_color",
            "label": "Color"
          },
          "term": {
            "internalId": 459,
            "slug": "default",
            "label": "Default"
          }
        }
      },
      "variant": {
        "id": 11,
        "productGroupId": 1,
        "productModel3dId": 3,
        "sizeId": 9,
        "materialId": 3,
        "printTypeId": 5,
        "colorId": 11,
        "metadata": null,
        "minOrderQuantity": null,
        "processingTime": null,
        "stockQuantity": null,
        "volume": null,
        "weight": null
      },
      "unitPrice": {
        "value": 0,
        "currency": "EUR"
      },
      "totalPrice": {
        "value": 0,
        "currency": "EUR"
      },
      "quantity": 1
    }
  ]
}