# Install Sign Customiser on a headless Shopify storefront

Connect Sign Customiser to a Storefront API or Hydrogen cart outside a Shopify Online Store theme.

- Source URL: https://www.signcustomiser.com/help/integrations/16766558-install-sign-customiser-on-a-headless-shopify-storefront/
- Markdown URL: https://www.signcustomiser.com/help/integrations/16766558-install-sign-customiser-on-a-headless-shopify-storefront.md
- Category: Integrations
- Last updated: 2026-09-01

## Article

This guide is for developers who manage a headless Shopify storefront.

It explains how to show Sign Customiser and add the generated product to the existing Shopify cart.

Do not use this guide for a custom Liquid template. Use [Choose a Shopify installation method](../../integrations/shopify-integration-and-theme-setup/) instead.

## Before you start

You need:

-   Sign Customiser connected to the same Shopify store.

-   The [Sign Customiser ID](../../integrations/find-your-sign-customiser-id/).

-   A Storefront API or Hydrogen cart integration.

-   The presentment currency and its conversion rate.

-   The active Shopify market when you use Shopify Markets.

Never put a private Storefront API token in browser code.

## 1\. Select the Shopify publication

Sign Customiser creates a new Shopify product when the customer selects **Add to Cart**.

The product must be available to the sales channel that serves your headless storefront.

1.  In Sign Customiser, go to **Tools & Settings** > **Integrations and Webhooks**.

2.  Open the Shopify integration.

3.  Select **Edit Integration**.

4.  Set **Publication** to the publication used by your headless storefront.

5.  Save the integration.

If you do not select a publication, Sign Customiser uses **Online Store** when it is available.

## 2\. Add the iframe

Build the iframe URL with `URLSearchParams`.

```html
<iframe
  id="sign-customiser"
  title="Sign customiser"
  style="border: 0; display: block; min-height: 800px; width: 100%"
></iframe>
```

```javascript
const signCustomiserOrigin = "https://web.signcustomiser.com";
const iframe = document.querySelector("#sign-customiser");const params = new URLSearchParams({
  client: "headless-shopify",
  currency_code: storefront.currencyCode,
  currency_rate: String(storefront.currencyRate),
});if (storefront.currencyFormat) {
  params.set("currency_format", storefront.currencyFormat);
}if (storefront.marketId && storefront.marketHandle) {
  params.set("shopify_market_id", String(storefront.marketId));
  params.set("shopify_market_handle", storefront.marketHandle);
}iframe.src = `${signCustomiserOrigin}/embed/${customiserId}?${params}`;
```

Use a positive `currency_rate`. Use `1` when the presentment currency is the store base currency.

The rate must convert a price from the store base currency to the presentment currency.

If you use Shopify Markets, send both `shopify_market_id` and `shopify_market_handle`.

## 3\. Send the storefront context

Listen for the `spcReady` message. Then send the current page and Shopify context to the iframe.

```javascript
function sendStorefrontContext() {
  iframe.contentWindow?.postMessage(
    {
      type: "setHostUrl",
      hostUrl: window.location.href,
      shopifyContext: {
        marketHandle: storefront.marketHandle,
        marketId: storefront.marketId,
      },
    },
    signCustomiserOrigin,
  );
}window.addEventListener("message", async (event) => {
  if (
    event.origin !== signCustomiserOrigin ||
    event.source !== iframe.contentWindow
  ) {
    return;
  }  if (event.data?.type === "spcReady") {
    sendStorefrontContext();
  }
});iframe.addEventListener("load", sendStorefrontContext);
```

The origin of `hostUrl` must match the origin of the page that sends this message.

## 4\. Connect the Shopify cart

Sign Customiser sends `sc:product:created` after it creates the Shopify product.

Use `product.external_data.variant_gid` as the Storefront API `merchandiseId`.

Copy every `productFormatted` entry to the cart-line attributes. Keep keys that start with an underscore.

```javascript
async function addSignCustomiserProduct(message) {
  const variantGid = message.product?.external_data?.variant_gid;  if (!variantGid) {
    throw new Error("The generated product has no Shopify variant GID.");
  }  const attributes = Object.entries(message.productFormatted ?? {}).map(
    ([key, value]) => ({ key, value: String(value ?? "") }),
  );  await storefrontCart.addLines([
    {
      merchandiseId: variantGid,
      quantity: 1,
      attributes,
    },
  ]);
}
```

`storefrontCart.addLines` represents your existing cart function. It must create a cart when no cart exists.

For Hydrogen, connect this function to the Hydrogen cart handler. For another framework, use `cartCreate` or `cartLinesAdd`.

Read Shopify's documentation for [Hydrogen cart handlers](https://shopify.dev/docs/api/hydrogen/latest/utilities/cart/createcarthandler) and [`cartLinesAdd`](https://shopify.dev/docs/api/storefront/latest/mutations/cartlinesadd).

Check the returned cart and its `userErrors`. Reject the operation when Shopify does not add the line.

## 5\. Report the cart result

Report the result before you open the cart or change the page.

Wait for the matching `sc:cart:result:ack` message. Use a short timeout so that reporting does not block the customer.

```javascript
function reportCartResult(checkoutAttemptId, result) {
  return new Promise((resolve) => {
    const timeout = window.setTimeout(finish, 400);    function finish() {
      window.clearTimeout(timeout);
      window.removeEventListener("message", receiveAck);
      resolve();
    }    function receiveAck(event) {
      if (
        event.origin === signCustomiserOrigin &&
        event.source === iframe.contentWindow &&
        event.data?.type === "sc:cart:result:ack" &&
        event.data.checkoutAttemptId === checkoutAttemptId
      ) {
        finish();
      }
    }    window.addEventListener("message", receiveAck);
    iframe.contentWindow?.postMessage(
      {
        type: "sc:cart:result",
        checkoutAttemptId,
        ...result,
      },
      signCustomiserOrigin,
    );
  });
}
```

## 6\. Handle the product-created event

Add this case to the validated message listener from step 3.

```javascript
if (event.data?.type === "sc:product:created") {
  const { checkoutAttemptId } = event.data;  try {
    await addSignCustomiserProduct(event.data);
    await reportCartResult(checkoutAttemptId, { outcome: "succeeded" });
    storefrontCart.open();
  } catch (error) {
    await reportCartResult(checkoutAttemptId, {
      outcome: "failed",
      classification: "unknown_failure",
    });    iframe.contentWindow?.postMessage(
      {
        type: "addToCartFailed",
        errorMessage: "We could not add this product to the cart. Try again.",
      },
      signCustomiserOrigin,
    );
  }
}
```

Keep the origin and iframe-window checks from step 3 around this code.

Classify known failures as `platform_rejected`, `platform_unavailable`, or `cart_unavailable`. Use `unknown_failure` for other failures.

Do not send exception messages or Shopify response bodies in the result message.

## 7\. Test the installation

Complete these checks before you publish the storefront:

1.  Open the customiser in each supported market and currency.

2.  Confirm that the displayed price uses the correct presentment currency.

3.  Create a design and select **Add to Cart**.

4.  Confirm that the generated variant is available to the headless storefront publication.

5.  Confirm that the cart contains one generated variant and all line attributes.

6.  Complete checkout and open the order in Shopify admin.

7.  Confirm that the design details and files are available for production.

If the generated variant is unavailable, check the **Publication** setting and the active Shopify market first.
