> For the complete documentation index, see [llms.txt](https://docs.tern.eco/v1/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tern.eco/v1/storefront-integration/storefront-integration/headless-storefront-third-party.md).

# Shopify headless storefront guide

## Purpose

This guide explains how a Shopify headless storefront should prepare a signed config payload, expose it to the Tern storefront module, and mount `<tern-trade-in>`.

This is the recommended integration path when the storefront can identify the shop and, optionally, the logged-in Shopify customer.

If you are not a Shopify merchant, use [Non-Shopify Headless Storefront Integration Guide](/v1/storefront-integration/storefront-integration/non-shopify-headless-storefront-third-party.md).

If you cannot use headless authentication at all, use [Standalone Storefront Integration Guide](/v1/storefront-integration/storefront-integration/standalone-storefront-third-party.md).

## Recommendation First

Use this guide when:

1. The storefront belongs to a Shopify shop.
2. You can generate merchant-signed bootstrap payloads on your backend.
3. You may need anonymous or logged-in customer bootstrap.
4. You want the recommended trust model for headless integrations.

## Summary

The implementation has three steps:

1. Get the merchant shared secret from Tern.
2. Use that shared secret on your backend to sign the bootstrap request payload.
3. Expose `window.ternStorefrontConfig` before mounting `<tern-trade-in>`.

## Shared Secret

Before implementing the bootstrap call, obtain the merchant shared secret from the Tern admin interface for the relevant shop.

Implementation guidance:

1. Generate or copy the shared secret from the Tern admin interface.
2. Store the shared secret on your backend.
3. Do not expose the shared secret in browser code.
4. Use the shared secret only on the server when generating the HMAC signature.

## Config Format

Expose `window.ternStorefrontConfig` with these fields:

1. `shopDomain`
2. `locale`
3. `proof`
4. Optional `customerLogin`

Browser config uses `shopDomain`. The storefront runtime converts that to the API field `shop_domain` before calling the bootstrap endpoint.

### Optional `customerLogin`

Use `customerLogin` when you want to override where unauthenticated customers are redirected for login.

`customerLogin` supports:

1. `path`: the login path on the current origin (must start with `/`)
2. `redirectParam`: query-string key used to return from login

If omitted, storefront uses:

1. `path = /account/login`
2. `redirectParam = checkout_url`

Example:

```json
{
  "shopDomain": "example.myshopify.com",
  "locale": "en",
  "customerLogin": {
    "path": "/account/login",
    "redirectParam": "checkout_url"
  },
  "proof": {
    "type": "merchant_signed",
    "timestamp": 1710000000,
    "signature": "hex-hmac-signature"
  }
}
```

## Supported Bootstrap Flows

### Merchant-Signed Anonymous Bootstrap

Use this when you want a signed storefront config without attaching a customer.

```json
{
  "shopDomain": "example.myshopify.com",
  "locale": "en",
  "proof": {
    "type": "merchant_signed",
    "timestamp": 1710000000,
    "signature": "hex-hmac-signature"
  }
}
```

### Merchant-Signed Logged-In Bootstrap

Use this when you want the storefront config to identify a logged-in customer.

```json
{
  "shopDomain": "example.myshopify.com",
  "locale": "en",
  "proof": {
    "type": "merchant_signed",
    "timestamp": 1710000000,
    "signature": "hex-hmac-signature",
    "customer": {
      "id": 7639131717921,
      "email": "customer@example.com"
    }
  }
}
```

## How To Sign The Request

When using `merchant_signed`, your backend must sign the full canonical string shown below with HMAC-SHA256 using the merchant shared secret.

The result of that HMAC operation becomes `proof.signature` in `window.ternStorefrontConfig`.

Only these four values are part of the signature:

1. `email`
2. `shop_domain`
3. `id`
4. `timestamp`

Sign this exact canonical string:

```
email=<normalized-email>&id=<id-or-0>&shop_domain=<shop-domain>&timestamp=<unix-seconds>
```

Rules:

1. `email` must be lowercased and trimmed.
2. `shop_domain` must be the Shopify shop domain for the target store.
3. `id` must be the logged-in customer id, or `0` when the bootstrap is anonymous.
4. `timestamp` must be a Unix timestamp in seconds.
5. The signature must be lowercase hex.
6. Generate the signature on your backend, never in browser code.

### Node.js Example: Anonymous Signing

```js
import { createHmac } from 'node:crypto';

const sharedSecret = process.env.TERN_SHARED_SECRET;
const timestamp = Math.floor(Date.now() / 1000);

const canonical = [
  ['email', ''],
  ['shop_domain', 'example.myshopify.com'],
  ['id', '0'],
  ['timestamp', String(timestamp)]
].map(([key, value]) => `${key}=${value}`).join('&');

const signature = createHmac('sha256', sharedSecret)
  .update(canonical, 'utf8')
  .digest('hex')
  .toLowerCase();

const payload = {
  shopDomain: 'example.myshopify.com',
  locale: 'en',
  proof: {
    type: 'merchant_signed',
    timestamp,
    signature
  }
};
```

### Node.js Example: Logged-In Customer Signing

```js
import { createHmac } from 'node:crypto';

const sharedSecret = process.env.TERN_SHARED_SECRET;
const timestamp = Math.floor(Date.now() / 1000);
const email = 'customer@example.com'.trim().toLowerCase();
const shopifyCustomerId = '7639131717921';

const canonical = [
  ['email', email],
  ['shop_domain', 'example.myshopify.com'],
  ['id', shopifyCustomerId],
  ['timestamp', String(timestamp)]
].map(([key, value]) => `${key}=${value}`).join('&');

const signature = createHmac('sha256', sharedSecret)
  .update(canonical, 'utf8')
  .digest('hex')
  .toLowerCase();

const payload = {
  shopDomain: 'example.myshopify.com',
  locale: 'en',
  proof: {
    type: 'merchant_signed',
    timestamp,
    signature,
    customer: {
      id: Number(shopifyCustomerId),
      email
    }
  }
};
```

In the logged-in example:

1. The signed string includes the normalized email and Shopify customer id.
2. The `customer` object in the request must match the values used to build the signed canonical string.
3. If these values do not match, Tern will reject the request.

For anonymous merchant-signed bootstrap, omit `proof.customer` entirely and sign with an empty `email` and `id=0`.

## Bootstrap Response

After the storefront module initializes, it obtains a bootstrap response that includes:

1. `token`
2. `locale`
3. `ga_measurement_id`
4. Optional `shopify_customer`

Example response:

```json
{
  "token": "tern-jwt",
  "locale": "en",
  "ga_measurement_id": "",
  "shopify_customer": {
    "id": 7639131717921,
    "first_name": "Ben",
    "last_name": "Yarwood",
    "email": "ben@tern.eco",
    "address": {}
  }
}
```

## Browser Runtime And Mount

Your page must load the Tern storefront runtime before you try to mount `<tern-trade-in>`.

If you are using the built browser bundle, load it first:

```html
<script src="https://prod.tern.eco/js/storefront/trn-nrc-umd.js"></script>
```

Then define `window.ternStorefrontConfig` before mounting `<tern-trade-in>`. The storefront module uses that config to initialize the storefront session internally.

### Example `head`

```html
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tern Shopify Headless Storefront</title>
  <script src="https://prod.tern.eco/js/storefront/trn-nrc-umd.js"></script>
</head>
```

### Example `body`

```html
<body>
  <div id="tern-storefront-root"></div>

  <script>
    window.ternStorefrontConfig = {
      shopDomain: 'example.myshopify.com',
      locale: 'en',
      customerLogin: {
        path: '/account/login',
        redirectParam: 'checkout_url'
      },
      proof: {
        type: 'merchant_signed',
        timestamp: 1710000000,
        signature: 'hex-hmac-signature',
        customer: {
          id: 7639131717921,
          email: 'customer@example.com'
        }
      }
    };

    const element = document.createElement('tern-trade-in');
    document.getElementById('tern-storefront-root').appendChild(element);
  </script>
</body>
```

## What The JavaScript Example Assumes

The JavaScript snippets in this guide are integration snippets, not complete standalone pages.

They assume:

1. Your page already includes the Tern storefront bundle, for example `https://prod.tern.eco/js/storefront/trn-nrc-umd.js`.
2. Your page already contains a mount target such as `<div id="tern-storefront-root"></div>`.
3. The HMAC signature was generated on your backend before the browser set `window.ternStorefrontConfig`.
4. The browser can load the Tern storefront bundle successfully.

If you paste the snippet into a page without those prerequisites, it will not work on its own.

## Implementation Checklist

1. Obtain the merchant shared secret from the Tern admin interface.
2. Store the shared secret on your backend.
3. Build and sign the canonical payload on the backend.
4. Expose `shopDomain`, `locale`, and `proof` in `window.ternStorefrontConfig` before mounting the storefront.
5. Optionally set `customerLogin.path` and `customerLogin.redirectParam` to control login redirect behavior.
6. Load the storefront browser bundle, for example `https://prod.tern.eco/js/storefront/trn-nrc-umd.js`.
7. Mount `<tern-trade-in>` after `window.ternStorefrontConfig` is defined.
8. Let the storefront module initialize the storefront session from that config.
9. Use the returned `token` from the bootstrap response as the storefront session token.
