Studio
Embed a live mockup editor in your storefront via iframe. Your server creates a session, your page embeds the editor with the returned token, and your page listens for postMessage events. The same flow powers both PSD customization and 2D product photo mockups.
Studio is the shared editor for both PSD templates and 2D mockups. One iframe, both engines. Your customers design on your storefront, and you never build a rendering pipeline.

<iframe> and a short-lived session. The customizer stays embedded on your product page and is bound to that page's exact origin.create-session call. Upload, placement, live preview, and the finished image are all handled inside Studio.The integration contract, in three parts
Studio lets your customers customize a mockup directly on your storefront, without leaving your page. It comes in two flavors that share the same integration shape:
PSD customizationmockup_type: psdOpen an iframe editor for a prepared PSD template. Bind the session to an existing mockup_uuid and the exact allowed_origin that embeds it.
2D product photomockup_type: 2dOpen the 2D customizer for a product photo mockup. Use setup for full editing access or customize for shoppers working on a ready mockup.
Both are created through the same POST /studio/create-session endpoint and both return an opaque session token for the iframe plus one-time handshake values for the trusted parent page. Only the session token belongs in the iframe URL.
/api/v1/studio/create-sessionAPI Key Never Exposed
sess_ + random characters). Your API key is stored server-side and never included in the token or any client response. Successful use refreshes the idle window, bounded by a four-hour absolute expiry.How It Works
Two 2D session kinds
customize: bind a ready
mockup_uuid. The shopper can place artwork, preview, render, and submit, but cannot edit the product selection or print areas.The typical storefront flow: prepare the mockup with a
setup session or the public 2D Mockups API, then open a customize session on that mockup_uuid with a product_id for every shopper.POST /studio/create-session with your API key (server-to-server). The API verifies ownership and generates an opaque token for one customization session.sess_xxx token to your frontend. The API key stays on your server.studio.sudomock.com/editor?session=sess_xxx in an iframe. The editor verifies the token, proves the parent origin, and loads the merchant config.window message events. Setup returns studio.mockup-saved; customize returns studio.design-submitted. The editor also reports ready, close, and error events.Setup opens the full editor
setup is a permission profile, not a sequence of stages. Studio prepares a new product automatically and opens the full editor with product editing, print-area editing, artwork placement, preview, render, and save available together.
Customize keeps administrative controls unavailable
customize session opens the same editor surface for a ready mockup_uuid, but it grants only artwork placement, preview, render, and submit. Labels and colors can change presentation; they never change these permissions.Authentication
Studio endpoints use three authentication schemes depending on the endpoint and caller:
| Scheme | Header / Mechanism | Used By |
|---|---|---|
| API Key | x-api-key: sm_your_api_key | Create and confirm Studio sessions from your server |
| Shopify HMAC | Shopify App Proxy signature (query params) | create-session (Shopify storefronts via App Proxy) |
POST /create-session
Generates an opaque session token for the Studio editor. Requires x-api-key header (WooCommerce, custom integrations) or Shopify App Proxy HMAC signature. The API key never leaves the server. One endpoint serves both mockup types, distinguished by the request body.
Headers
x-api-keystringYour SudoMock API key starting with sm_. Required unless using Shopify HMAC authentication. Used server-side only. Never expose it to the browser.
Content-TypestringRequiredMust be application/json
Request Body: PSD customization session
Bind the session to a prepared PSD-backed mockup by its mockup_uuidand embed it from the exact allowed_origin.
mockup_uuidstringRequiredUUID of the mockup to open in Studio. Must belong to the authenticated account.
allowed_originstringRequiredThe exact origin of the page that embeds the Studio iframe, for example https://shop.example.com.
product_idstringProduct identifier from your platform (e.g., Shopify Product GID, WooCommerce product ID). Stored in the session for your reference.
variant_idstringOptional variant identifier. Send the same value in the server-side result confirmation.
configobjectOptional configuration for this session. It accepts the same partial fields listed under Studio Configuration, without config_version. The override is not saved and requires x-api-key authentication.
1{2 "mockup_type": "psd",3 "session_kind": "customize",4 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",5 "allowed_origin": "https://shop.example.com",6 "product_id": "gid://shopify/Product/123456",7 "variant_id": "gid://shopify/ProductVariant/789",8 "action_id": "add-to-cart",9 "config": {10 "theme": "dark",11 "accentColor": "#3366FF",12 "psdPrimaryActionLabel": "Add to cart"13 }14}
Request Body: 2D product photo session
Open the 2D customizer for a product photo mockup. Set mockup_type to 2d and choose a session_kind. The editor always runs in an iframe embedded by allowed_origin. If your mockup is already prepared and you only want placement and rendering, use customize; product and print-area controls stay unavailable.
| What you want | session_kind | Access |
|---|---|---|
| Create or edit a mockup | setup | Full editing, placement, render, and save |
| Use a ready mockup in a storefront | customize | Artwork placement, render, and submit |
mockup_typeenumRequiredSet to 2d for the 2D customizer.
session_kindenumRequiredOne of setup or customize. Use setup for full editing access. Use customize for a shopper working on a ready mockup_uuid.
allowed_originstringRequiredThe exact origin of the page that will embed the iframe (for example https://shop.example.com). The customizer only runs when it is embedded by this origin.
product_idstringOptional product identifier from your platform. Use it to associate the session with your catalog.
variant_idstringOptional variant identifier. Send the same value in the server-side result confirmation.
mockup_uuidstringRequired for customize. Optional for setup, where omitting it starts a new mockup. Must be a valid UUID.
action_idstringOptional allowlisted routing identifier returned with the result event. Use 1–64 characters from A–Z, a–z, 0–9, dot, underscore, colon, and hyphen.
configobjectOptional configuration for this session. It accepts the same partial fields listed under Studio Configuration, without config_version. Omitted fields use the API key's saved settings. Send null for one field to use its theme default for this session. The override is not saved.
Configuration never grants permissions
x-api-keyrequests. It can change appearance, labels, and optional controls, but cannot change the session kind or grant setup capabilities.1{2 "mockup_type": "2d",3 "session_kind": "customize",4 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",5 "allowed_origin": "https://shop.example.com",6 "product_id": "SKU-1234",7 "variant_id": "SKU-1234-BLACK-M",8 "action_id": "add-to-cart",9 "config": {10 "theme": "dark",11 "accentColor": "#3366FF",12 "twoDCustomizePrimaryActionLabel": "Add to cart"13 }14}
1{2 "mockup_type": "2d",3 "session_kind": "setup",4 "allowed_origin": "https://shop.example.com",5 "product_id": "SKU-1234",6 "variant_id": "SKU-1234-BLACK-M",7 "action_id": "save-mockup",8 "config": {9 "theme": "light",10 "accentColor": "#3366FF",11 "twoDSetupPrimaryActionLabel": "Save mockup"12 }13}
A pre-built mockup is ready for a customize session
setup session) returns a mockup_uuidthat can be used to create a customize session. The shopper opens directly in artwork placement without administrative controls.Response
Every response includes mockup_type, an opaque session, and expires_in. Both PSD and 2D responses return the credentials required by the origin-proof message handshake.
1{2 "success": true,3 "mockup_type": "psd",4 "session": "sess_3_TU1FyqABbmj28yg_-YqENaK-CnvsxBuovgmiT_RHc",5 "expires_in": 900,6 "message_session_id": "fac13800-0273-4ea6-aef6-56c818383f0d",7 "bootstrap_secret": "dtiinV-YkyuD35zTViO2RIBHM4xNCaDJMXC2C4qiPN4"8}
1{2 "success": true,3 "mockup_type": "2d",4 "session": "sess_9adcc8281f4e4a2b9c3d2e6f8b0a1d77",5 "expires_in": 1800,6 "message_session_id": "fac13800-0273-4ea6-aef6-56c818383f0d",7 "bootstrap_secret": "dtiinV-YkyuD35zTViO2RIBHM4xNCaDJMXC2C4qiPN4"8}
successbooleanRequiredAlways true on success. Errors return HTTP exceptions.
sessionstringRequiredOpaque session token. Format: sess_ followed by URL-safe random characters. Pass this to the Studio URL. It carries no API key. Create a fresh session per customization; do not cache or reuse tokens across customers.
expires_inintegerRequiredInitial idle lifetime in seconds: 1,800 for setup and 900 for customize. Successful use refreshes the idle window, bounded by the four-hour absolute expiry.
mockup_typestringRequiredEchoes psd or 2d.
message_session_idstringRequiredBind every postMessage envelope to this session identifier.
bootstrap_secretstringRequiredDeliver to the trusted parent page for the origin-proof handshake. Never put it in the iframe URL or logs.
Studio is iframe-only
allowed_origin. Popup and top-level page modes are not supported because they cannot complete the parent-origin handshake.Error Responses
A required field is missing or a value is invalid: for example, a customize request without a mockup_uuid, an unsupported session kind, or a malformed origin.
1{2 "success": false,3 "error": {4 "code": "INVALID_SESSION_REQUEST",5 "message": "mockup_uuid is required for a customize session.",6 "retryable": false7 }8}
1{2 "success": false,3 "error": {4 "code": "AUTHENTICATION_FAILED",5 "message": "The API key is invalid or inactive.",6 "retryable": false7 }8}
The API key is invalid or has been deactivated. Signed Shopify requests return a standard detail response when the HMAC signature is missing, invalid, or expired.
1{2 "success": false,3 "error": {4 "code": "MOCKUP_NOT_AVAILABLE",5 "message": "The mockup is not available for this account.",6 "retryable": false7 }8}
The requested 2D mockup is unavailable to this account. UI overrides on a non-2D or non-API-key session also return a structured 403 response. PSD ownership failures use a standard detail response.
1{2 "success": false,3 "error": {4 "code": "STORE_NOT_CONNECTED",5 "message": "The store is not connected.",6 "retryable": false7 }8}
Shopify HMAC flow only: the shop domain is not linked to any SudoMock account.
1{2 "success": false,3 "error": {4 "code": "SETUP_REQUIRED",5 "message": "The mockup setup must be finalized before customization.",6 "retryable": false7 }8}
A customize session was requested before its 2D mockup was ready for shopper use. Finish the mockup in a setup session, then create a new customize session.
Code Examples
Call this endpoint from your backend so your API key stays server-side. The trusted parent page needs message_session_id and bootstrap_secretfor the origin-proof handshake. Put only the session token in the iframe URL and never log the bootstrap secret.
1curl -X POST "https://api.sudomock.com/api/v1/studio/create-session" \2 -H "Content-Type: application/json" \3 -H "x-api-key: sm_your_api_key" \4 -d '{5 "mockup_type": "psd",6 "session_kind": "customize",7 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",8 "allowed_origin": "https://shop.example.com",9 "product_id": "product-123",10 "variant_id": "variant-456",11 "action_id": "add-to-cart"12 }'
1curl -X POST "https://api.sudomock.com/api/v1/studio/create-session" \2 -H "Content-Type: application/json" \3 -H "x-api-key: sm_your_api_key" \4 -d '{5 "mockup_type": "2d",6 "session_kind": "customize",7 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",8 "allowed_origin": "https://shop.example.com",9 "product_id": "SKU-1234",10 "variant_id": "SKU-1234-BLACK-M"11 }'
Use setup when an administrator needs the complete editor. Omit mockup_uuid to create a new mockup, or provide it to edit an existing one.
1curl -X POST "https://api.sudomock.com/api/v1/studio/create-session" \2 -H "Content-Type: application/json" \3 -H "x-api-key: sm_your_api_key" \4 -d '{5 "mockup_type": "2d",6 "session_kind": "setup",7 "allowed_origin": "https://shop.example.com",8 "product_id": "SKU-1234",9 "variant_id": "SKU-1234-BLACK-M"10 }'
Embed the iframe
Load the customizer with the session token you just created. Size the iframe to fit your layout; the editor is responsive inside it.
1<iframe2 id="sudomock-studio"3 src="https://studio.sudomock.com/editor?session=SESS_TOKEN"4 allow="clipboard-write"5 style="width: 100%; height: 720px; border: 0; border-radius: 12px;"6 title="Customize your product"7></iframe>
Origin must match
allowed_origin you sent when creating the session. If they differ, the customizer will not start.postMessage Event Protocol
The embedded editor communicates with your page through the browser postMessage API. Add a single message listener on your page. Every message the editor sends is an object with a source of sudomock-studio, a type naming the event, and a payload with the event data.
Always verify the sender
event.source === iframe.contentWindow, protocol version, expected message_session_id, and a replay set keyed by request_id. An origin check alone is not enough when a page has more than one Studio frame.Event Reference
| type | When it fires | Payload fields |
|---|---|---|
studio.ready | The customizer has loaded and is interactive. | protocol_version |
studio.mockup-saved | The primary action is used in a setup session. | mockup_uuid, render_uuid, action_id? |
studio.design-submitted | The primary action is used in a customize session. | mockup_uuid, render_uuid, action_id? |
studio.close | The customer closed the customizer. | reason (completed | cancelled) |
studio.error | Something went wrong during the session. | code, message, retryable |
Result Events
Setup emits studio.mockup-saved; customize emits studio.design-submitted. Both carry only stable identifiers needed for integration. Forward the event to your server, confirm it with POST /api/v1/studio/actions/consume, use action_id to route the confirmed result to your own allowlisted action, then acknowledge it with parent.action-result. URLs, session tokens, API keys, proof keys, and source artwork are never callback fields.
A browser event is not an authorization result
POST /api/v1/studio/actions/consume with its API key. The endpoint binds the event to the merchant, session, and successful render, then returns the same receipt for an exact retry.mockup_uuidstringThe mockup template this result was produced from.
render_uuidstringIdentifies this exact customized result. Store it on the cart line so the order references the right output.
action_idstringOptional allowlisted routing identifier supplied when the session was created.
1{2 "version": 1,3 "source": "sudomock-studio",4 "type": "studio.design-submitted",5 "request_id": "9b6964ec-b2d3-4c06-86e8-188875cb43ad",6 "message_session_id": "fac13800-0273-4ea6-aef6-56c818383f0d",7 "payload": {8 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",9 "render_uuid": "9adcc828-1f4e-4a2b-9c3d-2e6f8b0a1d77",10 "action_id": "add-to-cart"11 }12}
Confirming a Result on Your Server
Send the event envelope without its browser-only source field. Add action_context from your authenticated server state, not from browser input. Its product and variant values must exactly match the values bound when the session was created. The API key stays on your server.
1{2 "version": 1,3 "type": "studio.design-submitted",4 "request_id": "9b6964ec-b2d3-4c06-86e8-188875cb43ad",5 "message_session_id": "fac13800-0273-4ea6-aef6-56c818383f0d",6 "payload": {7 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",8 "render_uuid": "9adcc828-1f4e-4a2b-9c3d-2e6f8b0a1d77",9 "action_id": "add-to-cart",10 "action_context": {11 "product_id": "SKU-1234",12 "variant_id": "SKU-1234-BLACK-M"13 }14 }15}
1{2 "success": true,3 "replayed": false,4 "receipt": {5 "version": 1,6 "request_id": "9b6964ec-b2d3-4c06-86e8-188875cb43ad",7 "message_session_id": "fac13800-0273-4ea6-aef6-56c818383f0d",8 "type": "studio.design-submitted",9 "mockup_type": "2d",10 "session_kind": "customize",11 "action_id": "add-to-cart",12 "action_context": {13 "product_id": "SKU-1234",14 "variant_id": "SKU-1234-BLACK-M"15 },16 "mockup_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",17 "render_uuid": "9adcc828-1f4e-4a2b-9c3d-2e6f8b0a1d77"18 }19}
Listening for Events
A single listener handles the whole protocol. Route on message.type and act on the ones your integration needs.
1const STUDIO_ORIGIN = "https://studio.sudomock.com";2const studioFrame = document.querySelector("#sudomock-studio");3const expectedMessageSessionId = createSessionResponse.message_session_id;4const seen = new Set();56// bootstrapSecret is the bootstrap_secret from your server's create-session response.7// Deliver it only to this trusted parent page; never put it in the iframe URL or logs.8// It is relayed to the editor only in the handshake below. A fresh 22-char base64url nonce:9const nonce = () =>10 btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))))11 .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");1213window.addEventListener("message", async (event) => {14 if (event.origin !== STUDIO_ORIGIN || event.source !== studioFrame.contentWindow) return;1516 const message = event.data;17 if (18 !message ||19 message.version !== 1 ||20 message.source !== "sudomock-studio" ||21 message.message_session_id !== expectedMessageSessionId ||22 typeof message.request_id !== "string" ||23 seen.has(message.request_id)24 ) return;25 seen.add(message.request_id);2627 switch (message.type) {28 case "studio.ready":29 // REQUIRED origin-proof handshake: reply so the editor can open. Skip this and the30 // customizer stays on "The customizer could not be opened." bootstrapSecret comes31 // from your create-session response; child_nonce is echoed back verbatim.32 event.source.postMessage({33 version: 1,34 source: "sudomock-parent",35 type: "parent.bootstrap",36 request_id: message.request_id,37 message_session_id: message.message_session_id,38 payload: {39 child_nonce: message.payload.child_nonce,40 parent_nonce: nonce(),41 bootstrap_secret: bootstrapSecret,42 },43 }, STUDIO_ORIGIN);44 break;4546 case "studio.mockup-saved":47 case "studio.design-submitted": {48 const p = message.payload;49 const { source: _source, ...actionEvent } = message;50 const response = await fetch("/api/studio/action", {51 method: "POST",52 headers: { "content-type": "application/json" },53 body: JSON.stringify({ event: actionEvent }),54 });55 const success = response.ok;56 if (!success) seen.delete(message.request_id);57 event.source.postMessage({58 version: 1,59 source: "sudomock-parent",60 type: "parent.action-result",61 request_id: message.request_id,62 message_session_id: expectedMessageSessionId,63 payload: { render_uuid: p.render_uuid, success },64 }, STUDIO_ORIGIN);65 break;66 }6768 case "studio.close":69 // The customer closed the customizer (reason: completed | cancelled).70 closeCustomizerModal();71 break;7273 case "studio.error":74 // Something went wrong. retryable tells you if a retry may help.75 console.error(message.payload.code, message.payload.message);76 break;77 }78});
Make it yours
Your logo, your colors, your buttons, your theme. It looks like part of your store, not a plugin. Set each through the config below. See White-label configuration for the full key reference.
Studio Configuration
Store the default appearance and controls per API key with GET /api/v1/studio/config and PUT /api/v1/studio/config. Both calls require your x-api-key header and belong on your server. The same settings are available in Dashboard → Studio settings.
| Method | Behavior |
|---|---|
GET /api/v1/studio/config | Returns the complete effective configuration and its config_version. |
PUT /api/v1/studio/config | Sends config_version plus a partial config. Send null to reset one override to its default. |
Updates are versioned
config_version returned by GET or PUT. A stale write returns 409 without changing the saved configuration.1{2 "config_version": 4,3 "config": {4 "theme": "light",5 "accentColor": "#DA7756",6 "psdPrimaryActionLabel": "Add to Cart",7 "twoDSetupPrimaryActionLabel": "Save Mockup",8 "twoDCustomizePrimaryActionLabel": "Add to Cart",9 "twoDShowExport": false10 }11}
1{2 "config_version": 5,3 "config": {4 "theme": "light",5 "primaryColor": "#0F172A",6 "accentColor": "#DA7756",7 "backgroundColor": "#F1F5F9",8 "panelBackground": "#FFFFFF",9 "textColor": "#0F172A",10 "borderColor": "#E2E8F0",11 "successColor": "#16A34A",12 "borderRadius": 10,13 "logoUrl": null,14 "fontFamily": null,15 "headerText": "Customize Your Design",16 "uploadText": "Drop image or click to upload",17 "loadingText": "Adding...",18 "successText": "Added!",19 "secondaryActionLabel": "Render Preview",20 "psdPrimaryActionLabel": "Add to Cart",21 "twoDSetupPrimaryActionLabel": "Save Mockup",22 "twoDCustomizePrimaryActionLabel": "Add to Cart"23 }24}
Shared branding and labels
| Key | Default | What it controls |
|---|---|---|
theme | light | Light or dark base palette. Explicit colors take precedence. |
primaryColor | #0F172A | Primary UI color. |
accentColor | #DA7756 | Actions and selected states. |
backgroundColor | #F1F5F9 | Editor page background. |
panelBackground | #FFFFFF | Editor panel background. |
textColor | #0F172A | Editor text. |
borderColor | #E2E8F0 | Borders and dividers. |
successColor | #16A34A | Successful final action. |
borderRadius | 10 | Corner radius from 0 to 20 pixels. |
logoUrl | null | HTTPS logo URL. |
fontFamily | null | Font family or fallback list. |
headerText | Customize Your Design | Editor heading. Empty hides it. |
uploadText | Drop image or click to upload | Upload prompt. |
loadingText | Adding... | Final-action loading state. |
successText | Added! | Final-action success state. |
secondaryActionLabel | Render Preview | PSD preview action. |
maxFileSize | 15 | Maximum PSD and 2D upload size in MB, from 1 to 50. |
PSD Editor settings
| Key | Default | What it controls |
|---|---|---|
psdPrimaryActionLabel | Add to Cart | Final action label. |
psdShowAdjustments | true | Adjustment controls. |
psdShowColorOverlay | true | Color overlay controls. |
psdShowTextLayers | true | Editable text layer controls. |
psdShowFitMode | true | Artwork fit choices. |
psdShowPosition | true | Position controls. |
psdShowSize | true | Size controls. |
psdShowRotation | true | Rotation control. |
psdShowFlip | true | Flip actions. |
psdShowExportOptions | true | Format, size, and quality controls. |
psdShowZoomControls | true | Canvas zoom controls. |
psdShowUndoRedo | true | Undo and redo. |
psdAutoRender | true | Render after edits settle. |
psdAutoRenderDelay | 800 | Auto-render delay from 300 to 3000 ms. |
psdLayout | full | Full or compact layout. |
2D Mockups settings
| Key | Default | What it controls |
|---|---|---|
twoDSetupPrimaryActionLabel | Save Mockup | Final action label in setup sessions. |
twoDCustomizePrimaryActionLabel | Add to Cart | Final action label in customize sessions. |
twoDShowArtwork | true | Artwork upload and replacement. |
twoDShowFill | true | Solid fill control. Artwork or fill must remain available. |
twoDShowBlend | true | Blend choices. |
twoDShowOpacity | true | Artwork opacity. |
twoDShowTransform | true | Position, size, and rotation controls. |
twoDShowZoom | true | Canvas zoom controls. |
twoDShowExport | true | Preview download action. |
twoDShowBackgroundRemoval | true | Background removal when the session allows it. |
To adapt one iframe to the parent page's current theme, send a partial config in POST /api/v1/studio/create-session. It accepts the same keys above, does not use config_version, and is not saved. Omitted keys keep the API key's saved settings; null restores that field's theme default for this session.
Capabilities stay server-controlled
Security Model
Studio sessions are designed with a zero-trust approach to API key protection:
Shopify and WooCommerce
The pattern is identical on hosted platforms. Create the session from your platform backend so the API key stays server-side, then embed the iframe on the product page and route the result event to your platform backend.
ShopifypatternCreate the session from your app backend, bind action_id to an allowlisted cart action, and resolve the returned render_uuid on your server before adding the line item.
WooCommercepatternCreate the session from PHP on your server, bind action_id to an allowlisted cart action, and store the returned render_uuid in cart-item metadata.