Customizer – Communication via postMessage

Introduction

Customizer communique avec la page hôte via postMessage. Il utilise la négociation commune avec un nonce, puis une demande de session propre à Customizer qui renvoie à la fois un jeton et une clé de panier.

Configuration :

  1. Ouvrez l’onglet Paramètres e-commerce dans l’application Alter Product.
  2. Dans la section Intégration, définissez le domaine sur lequel l’iframe sera intégré (par exemple https://yourwebsite.com ou http://localhost:3000).

Déroulement :

  1. Votre site charge l’iframe Customizer.
  2. L’iframe envoie ALTER_CHILD_HELLO ; votre page répond par ALTER_PARENT_ACK avec le même nonce.
  3. L’iframe demande une session avec ALTER_CUSTOMIZER_INIT_SESSION et des données contenant alterProductId, uiDesignId et mode.
  4. Votre serveur crée la session via l’API publique d’Alter Product et votre page répond par ALTER_CUSTOMIZER_SESSION_READY, contenant token, cartKey et mode.
  5. Après un enregistrement réussi, Customizer émet ALTER_CUSTOMIZER_ADD_TO_CART pour les nouvelles lignes du panier ou ALTER_CUSTOMIZER_UPDATE_DESIGN pour les lignes existantes.

Les métadonnées des variantes définies dans la configuration du produit sont incluses dans les données transmises.

Messages envoyés par Customizer

TypeDescription
ALTER_CHILD_HELLOInitialisation de la négociation (contient un nonce)
ALTER_CUSTOMIZER_INIT_SESSIONDemande un jeton de session Customizer et une clé de panier au site hôte
ALTER_CUSTOMIZER_ADD_TO_CARTL’utilisateur a cliqué sur « Ajouter au panier » (les données contiennent le design/la commande enregistrés)
ALTER_CUSTOMIZER_UPDATE_DESIGNL’utilisateur a enregistré les modifications d’une ligne de panier/d’une commande existante
ALTER_WP_CUSTOMIZER_ORDER_REQUESTDemande WordPress/WooCommerce de chargement d’une commande client existante
ALTER_WP_CUSTOMIZER_ORDER_SAVE_REQUESTDemande WordPress/WooCommerce d’enregistrement des données et des fichiers téléversés d’une commande client
ALTER_WP_RUNTIME_CONTEXT_REQUESTDemande du contexte d’exécution WordPress/WooCommerce
ALTER_WP_LOCAL_DESIGN_REQUESTDemande d’initialisation d’un design local WordPress lorsque wp_local_design=1

Lorsque le client clique sur Ajouter au panier dans Customizer, une commande est créée avec le statut par défaut shopping_cart. Tant que la commande a le statut shopping_cart, le client peut continuer à modifier le design (par exemple via un lien tel que https://alterproduct.com/app/customizer/{customizerId}/{orderId}). Dès que le statut devient différent de shopping_cart ou de editable, la modification est bloquée pour le client, qui voit alors une version en lecture seule du design.

Messages reçus par Customizer

TypeDescription
ALTER_PARENT_ACKConfirmation de la négociation (doit inclure le nonce)
ALTER_CUSTOMIZER_SESSION_READYFournit token, cartKey et mode pour autoriser l’accès à Customizer
ALTER_CUSTOMIZER_SESSION_ERRORÉchec de la demande de session Customizer
ALTER_WP_CUSTOMIZER_ORDER_RESPONSERéponse WordPress/WooCommerce contenant une commande client existante
ALTER_WP_CUSTOMIZER_ORDER_SAVE_RESPONSERéponse WordPress/WooCommerce après l’enregistrement d’une commande client
ALTER_WP_RUNTIME_CONTEXT_RESPONSERéponse contenant le contexte d’exécution WordPress/WooCommerce
ALTER_WP_LOCAL_DESIGN_RESPONSERéponse d’initialisation d’un design local WordPress

Exemple complet (HTML + JS)

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8" />
5  <meta name="viewport" content="width=device-width, initial-scale=1" />
6  <title>Alter Product Customizer — postMessage (Handshake + Token)</title>
7  <style>
8    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
9    iframe { width: 100%; height: 720px; border: 0; display: block; border-radius: 12px; }
10    pre { background: #0b1020; color: #d7e1ff; padding: 12px; border-radius: 12px; overflow:auto; }
11  </style>
12</head>
13<body>
14  <h1>My E-commerce</h1>
15
16  <iframe
17    id="customizerWidget"
18    src="https://alterproduct.com/app/customizer/1"
19    title="Alter Product Customizer"
20    allowfullscreen>
21  </iframe>
22
23  <h3>Logs</h3>
24  <pre id="log"></pre>
25
26  <script>
27    const IFRAME_ORIGIN = 'https://alterproduct.com';
28    const iframe = document.getElementById('customizerWidget');
29    const logEl = document.getElementById('log');
30
31    let handshakeOk = false;
32
33    function log(...args) {
34      const line = args.map(a => (typeof a === 'string' ? a : JSON.stringify(a, null, 2))).join(' ');
35      logEl.textContent += line + '\n';
36    }
37
38    function postToIframe(payload) {
39      iframe.contentWindow.postMessage(payload, IFRAME_ORIGIN);
40    }
41
42    /**
43     * Your backend owns this endpoint and should create a Customizer embed session
44     * using server-side credentials. Expected response:
45     * { sessionToken: "...", cartKey: "...", mode: "view" | "edit" }
46     */
47    async function getCustomizerSessionFromYourBackend(payload) {
48      const res = await fetch('/api/alter/customizer-session', {
49        method: 'POST',
50        headers: { 'Content-Type': 'application/json' },
51        body: JSON.stringify(payload || {}),
52      });
53
54      if (!res.ok) throw new Error('Failed to create customizer session');
55
56      const data = await res.json();
57      if (!data || !data.sessionToken || !data.cartKey) {
58        throw new Error('Missing sessionToken or cartKey');
59      }
60
61      return data;
62    }
63
64    window.addEventListener('message', async (event) => {
65      // ✅ 1) Validate origin
66      if (event.origin !== IFRAME_ORIGIN) return;
67
68      // ✅ 2) Validate source
69      if (event.source !== iframe.contentWindow) return;
70
71      const msg = event.data || {};
72      if (!msg.type || typeof msg.type !== 'string') return;
73
74      // -----------------------
75      // A) HANDSHAKE
76      // Customizer -> Host: ALTER_CHILD_HELLO { nonce }
77      // Host       -> Customizer: ALTER_PARENT_ACK { nonce }
78      // -----------------------
79      if (msg.type === 'ALTER_CHILD_HELLO') {
80        const nonce = msg.nonce;
81        if (!nonce || typeof nonce !== 'string') return;
82
83        handshakeOk = true;
84        log('[Customizer] -> Host: ALTER_CHILD_HELLO', { nonce });
85
86        log('[Host] -> Customizer: ALTER_PARENT_ACK');
87        postToIframe({ type: 'ALTER_PARENT_ACK', nonce });
88        return;
89      }
90
91      // Ignore everything until handshake is done
92      if (!handshakeOk) return;
93
94      // -----------------------
95      // B) CUSTOMIZER SESSION INIT
96      // Customizer -> Host: ALTER_CUSTOMIZER_INIT_SESSION { payload: { alterProductId, uiDesignId, mode } }
97      // Host       -> Customizer: ALTER_CUSTOMIZER_SESSION_READY { token, cartKey, mode }
98      // -----------------------
99      if (msg.type === 'ALTER_CUSTOMIZER_INIT_SESSION') {
100        try {
101          const payload = msg.payload || {};
102          const alterProductId = Number(payload.alterProductId || 0);
103          const uiDesignId = Number(payload.uiDesignId || 0);
104          const mode = payload.mode === 'edit' ? 'edit' : 'view';
105
106          log('[Customizer] -> Host: ALTER_CUSTOMIZER_INIT_SESSION', {
107            alterProductId,
108            uiDesignId,
109            mode,
110          });
111
112          const session = await getCustomizerSessionFromYourBackend({
113            alterProductId,
114            uiDesignId,
115            mode,
116          });
117
118          log('[Host] -> Customizer: ALTER_CUSTOMIZER_SESSION_READY');
119          postToIframe({
120            type: 'ALTER_CUSTOMIZER_SESSION_READY',
121            token: session.sessionToken,
122            cartKey: session.cartKey,
123            mode: session.mode || mode,
124          });
125        } catch (e) {
126          log('[Host] -> Customizer: ALTER_CUSTOMIZER_SESSION_ERROR', String(e && e.message ? e.message : e));
127          postToIframe({
128            type: 'ALTER_CUSTOMIZER_SESSION_ERROR',
129            message: String(e && e.message ? e.message : e),
130          });
131        }
132        return;
133      }
134
135      // -----------------------
136      // C) Customizer events
137      // -----------------------
138      if (msg.type === 'ALTER_CUSTOMIZER_ADD_TO_CART') {
139        log('[Customizer] -> Host: ALTER_CUSTOMIZER_ADD_TO_CART', msg.payload);
140        // Send payload to your backend and add item to cart in your e-commerce system
141        return;
142      }
143
144      if (msg.type === 'ALTER_CUSTOMIZER_UPDATE_DESIGN') {
145        log('[Customizer] -> Host: ALTER_CUSTOMIZER_UPDATE_DESIGN', msg.payload);
146        // Update an existing cart line/order in your e-commerce system
147        return;
148      }
149    });
150  </script>
151</body>
152</html>

Exemple de données

{
  "id": 481,
  "customizerId": 10,
  "userDesignId": 10,
  "isSeenByOwner": false,
  "orderStatus": "shopping_cart",
  "createdAt": "2026-05-28T10:15:00.000Z",
  "customizerOrderURL": "https://alterproduct.com/app/customizer/10/481",
  "customizerURL": "https://alterproduct.com/app/customizer/10",
  "customizerName": "Mug 450ml (15oz)",
  "totalPrice": {
    "value": 24.99,
    "currency": "EUR"
  },
  "productGroup": {
    "id": 1,
    "name": {
      "pl": "Kubek 450ml (15oz)",
      "en": "Mug 450ml (15oz)"
    }
  },
  "productItems": [
    {
      "id": 901,
      "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"
          }
        }
      },
      "printType": {
        "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"
          }
        }
      },
      "printingMethod": {
        "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",
        "customColor": false,
        "pickedColors": {},
        "patternId": null,
        "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,
        "printingMethodId": 5,
        "colorId": 11,
        "metadata": {
          "sku": "MUG-450-WHITE"
        },
        "minOrderQuantity": 1,
        "processingTime": null,
        "stockQuantity": null,
        "volume": null,
        "weight": null
      },
      "unitPrice": {
        "value": 24.99,
        "currency": "EUR"
      },
      "totalPrice": {
        "value": 24.99,
        "currency": "EUR"
      },
      "quantity": 1,
      "mockups": [
        {
          "productModel3dId": 3,
          "img": {
            "thumb": "https://api.example.com/file/protected/user_1/10/e-commerce/orders/481/mockup-thumb.webp",
            "large": "https://api.example.com/file/protected/user_1/10/e-commerce/orders/481/mockup-large.webp"
          }
        }
      ],
      "updatedAt": "2026-05-28T10:15:10.000Z"
    }
  ],
  "cartKey": "wc_cart_item_key"
}