> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Session Sync

> Set up checkout session syncing for a Catalyst storefront and troubleshoot session-sync failures such as "Invalid JWT token" errors and 404 responses.

Catalyst keeps a shopper's login state in sync between the Catalyst storefront and the Stencil checkout page, in both directions:

* A shopper who signs in on the Catalyst storefront stays signed in when they proceed to checkout. This was added in [Catalyst 0.19.0](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcatalyst-core%400.19.0).
* A shopper who signs in or out from the checkout page has that state carried back to the Catalyst storefront. This is built into Catalyst 1.0.0 and later, and requires the channel configuration described in [Set up session sync](#set-up-session-sync).

This page describes the intended behavior, the one-time setup, and how to troubleshoot session-sync failures.

## Intended behavior

### Login flow

1. The shopper navigates to Stencil Checkout as a **guest**.
2. They click **Sign In**.
3. They’re redirected to the **login page on the Catalyst storefront**.
4. After signing in, they’re redirected back to **Stencil Checkout**, now **signed in**.

### Logout flow

1. The shopper navigates to Stencil Checkout while **signed in**.
2. They click **Sign Out**.
3. They’re redirected to the **logout route on the Catalyst storefront**.
4. After signing out, they’re redirected back to **Stencil Checkout**, now as a **guest**.

## Set up session sync

1. **Use Catalyst 1.0.0 or later.**

   Checkout login and logout redirects are built into Catalyst 1.0.0 and later. If your project is on an earlier version, [upgrade](/developer/docs/storefront/catalyst/getting-started/upgrading) it, or merge [PR #2182](https://github.com/bigcommerce/catalyst/pull/2182) and [PR #2186](https://github.com/bigcommerce/catalyst/pull/2186) into it.

2. **Configure login and logout routes.**

   Send a request to [GET a Channel Site](/developer/api-reference/rest/admin/management/channels/site/get-channel-site) to retrieve the `site_id` for your Catalyst storefront.

   ```bash showLineNumbers={false}
   GET https://api.bigcommerce.com/stores/{store_hash}/v3/sites
   ```

   Send a request to [PUT Routes](/developer/api-reference/rest/admin/management/sites/site-routes/update-site-routes) to configure the login and logout routes.

   ```bash showLineNumbers={false}
   PUT https://api.bigcommerce.com/stores/{store_hash}/v3/sites/{site_id}/routes
   ```

   ```json showLineNumbers={false}
   [
     {
       "type": "home",
       "matching": "*",
       "route": "/"
     },
     {
       "type": "cart",
       "matching": "*",
       "route": "/cart/"
     },
     {
       "type": "login",
       "matching": "*",
       "route": "/login/"
     },
     {
       "type": "logout",
       "matching": "*",
       "route": "/logout/"
     },
     // Optional: Included by One-Click Catalyst but not required for session syncing
     {
       "type": "search",
       "matching": "*",
       "route": "/search/"
     },
     {
       "type": "account",
       "matching": "*",
       "route": "/account/"
     }
   ]
   ```

3. **Enable storefront redirection in checkout settings.**

   To enable storefront redirection in checkout settings, send a request to [PUT Checkout Settings](/developer/api-reference/rest/admin/management/checkouts/settings/update-checkout-settings) to update the `should_redirect_to_storefront_for_auth` setting.

   ```bash showLineNumbers={false}
   PUT https://api.bigcommerce.com/stores/{store_hash}/v3/checkouts/settings/channels/{channel_id}
   ```

   ```json showLineNumbers={false}
   {
     // Required to enable session-based redirect
     "should_redirect_to_storefront_for_auth": true,

     // Required if no custom checkout settings exist for this channel
     "custom_checkout_script_url": "",
     "custom_order_confirmation_script_url": "",
     "order_confirmation_use_custom_checkout_script": false
   }
   ```

## Troubleshooting session sync

When a shopper starts checkout, Catalyst calls the `createCartRedirectUrls` GraphQL mutation, which returns a `redirectedCheckoutUrl` of the form `https://<your-domain>/session-sync?jwt=...`. The `/session-sync` endpoint belongs to BigCommerce's checkout system, not to your Next.js app: BigCommerce validates the short-lived JWT it issued, transfers the cart and customer session, and then forwards the shopper to checkout.

Because that endpoint sits on the boundary between your Catalyst app and BigCommerce's checkout servers, a misconfiguration on either side surfaces as the same generic `Invalid JWT token` message or a `404` response. The following sections cover the failure modes in the order they're worth ruling out.

> **Info**
>
> After three consecutive requests with an invalid session-sync JWT, BigCommerce blocks the originating IP address for five minutes. Wait out the block before retesting, or your next attempt will fail regardless of the fix.

### Edge runtime in a checkout or login token route

**Symptom:** `Invalid JWT token`, or a runtime crash immediately after the checkout redirect. Server logs show an exception from `crypto` or `jose`.

**Cause:** `export const runtime = 'edge'` is present in `app/[locale]/(default)/checkout/route.ts` or `app/[locale]/(default)/(auth)/login/token/[token]/route.ts`. These routes sign and verify JWTs with Node.js APIs that the edge runtime does not provide.

**Fix:** Remove the `export const runtime = 'edge'` declaration from those routes so they run in the default Node.js runtime. Also check any catch-all route or wrapper that applies the edge runtime more broadly, and search the whole `app` directory rather than only the login and checkout paths:

```bash showLineNumbers={false}
grep -rn "runtime = 'edge'" app
```

### BIGCOMMERCE\_STOREFRONT\_TOKEN is not a storefront token

**Symptom:** GraphQL Storefront API calls return `401` before the shopper reaches checkout, or the storefront renders with missing data. Recent Catalyst versions log an explicit `InvalidStorefrontTokenError`.

**Cause:** `BIGCOMMERCE_STOREFRONT_TOKEN` holds an OAuth access token from an API account — an opaque string — instead of a GraphQL Storefront API token. The GraphQL Storefront API accepts only storefront tokens, which are JWTs made up of three base64url-encoded segments separated by periods (`xxxxx.yyyyy.zzzzz`).

**Fix:** Send a request to [Create a private token](/developer/api-reference/rest/admin/authentication-apis/storefront-api-tokens/private-api-token/create-private-token) with the `Unauthenticated` and `Customer` scopes, scoped to the channel your storefront uses. Set the returned token as `BIGCOMMERCE_STOREFRONT_TOKEN` and redeploy. For the full variable reference, including the API account scope required to mint the token, see [Environment variables](/developer/docs/storefront/catalyst/reference/environment-variables#bigcommerce_storefront_token).

### Channel ID mismatch between the environment and the domain configuration

**Symptom:** `Invalid JWT token` at `/session-sync`. The decoded JWT's `channel_id` claim doesn't match the channel that has your custom domain configured, and `redirect_to` points at a different domain than the one the shopper is on — for example, a production checkout URL while the request hits a sandbox subdomain.

**Cause:** `BIGCOMMERCE_CHANNEL_ID` points at one channel while the custom storefront domain is registered under another. BigCommerce issues the session-sync JWT for the channel that Catalyst names in the mutation, so if that channel's checkout domain doesn't match the host receiving the request, validation fails.

**Fix:**

1. In the control panel, open **Channel Manager** and select the channel that has the custom domain configured. The channel ID appears in the page URL.
2. Set `BIGCOMMERCE_CHANNEL_ID` to that channel ID exactly.
3. Regenerate `BIGCOMMERCE_STOREFRONT_TOKEN` scoped to the same channel ID.
4. Redeploy.

> **Warning**
>
> Give each environment its own channel with its own `BIGCOMMERCE_CHANNEL_ID` and `BIGCOMMERCE_STOREFRONT_TOKEN`. Sharing a channel between a sandbox and a production storefront guarantees a domain mismatch at session sync.

### Custom domain is not the primary storefront URL for the channel

**Symptom:** `/session-sync` returns a `404`. The JWT's `redirect_to` claim points at a domain other than the one the shopper is browsing.

**Cause:** The domain was added as an additional or redirect domain instead of the channel's primary storefront URL, or it was added to a channel other than the one `BIGCOMMERCE_CHANNEL_ID` names.

**Fix:** In the control panel, go to **Channel Manager → \[your channel] → Domains** and set the custom domain as the primary storefront URL. Confirm no other active channel claims the same domain. After saving, allow 10–15 minutes for the checkout system to pick up the change before you retest.

### DNS is not fully propagated

**Symptom:** The subdomain resolves inconsistently, or the session-sync request returns a connection error or a generic CDN `404` that doesn't come from BigCommerce's checkout system.

**Cause:** Nameserver or record changes for the domain haven't propagated to BigCommerce's infrastructure yet.

**Fix:**

1. Confirm the domain resolves to the servers BigCommerce expects — typically the same Cloudflare addresses as your other BigCommerce-hosted storefront domains:

   ```bash showLineNumbers={false}
   dig <your-subdomain> +short
   ```

2. Check the record with a DNS propagation checker to confirm it's visible globally. Full propagation can take up to 48 hours, depending on the registrar and the previous TTL.

3. Load the storefront homepage on the custom domain and confirm it renders. If the homepage is broken, session sync will fail too.

### Domain verification is still pending

**Symptom:** The domain shows as `pending` or unverified under the channel's domain settings, and the checkout system doesn't recognize it.

**Cause:** BigCommerce verifies ownership of a custom domain before enabling it for checkout. The domain can be live in your Catalyst environment before that verification completes.

**Fix:** Publish the verification record shown in the control panel — usually a `CNAME` or `TXT` record — and wait for the status to change to active. Don't test checkout until it does. For Native Hosting projects, `catalyst domains add` prints the records to publish and `catalyst domains status` reports the current state; see [Custom Domains](/developer/docs/storefront/catalyst/deployment/native-hosting/custom-domains).

### JWT expiry under slow redirect conditions

**Symptom:** Intermittent `Invalid JWT token` errors that correlate with slow networks or high server load, and that don't reproduce consistently.

**Cause:** A session-sync JWT is valid for 30 seconds. If a slow redirect chain, middleware delay, or client-side navigation wrapper sits between Catalyst generating the URL and the browser reaching `/session-sync`, the token can expire before BigCommerce validates it.

**Fix:** Redirect to `redirectedCheckoutUrl` from the server in a single hop, immediately after the mutation returns. Don't route the checkout redirect through client-side navigation logic, and check your [proxy chain](/developer/docs/storefront/catalyst/development/middleware) for anything that buffers or delays the redirect response. Request the URL as part of the checkout action rather than caching it — a cached `redirectedCheckoutUrl` is expired by the time a shopper uses it.

### Decode the session-sync JWT

The session-sync JWT is a standard JWT: three base64url-encoded segments separated by periods. You only need to read its claims, so decode the payload — the second segment — without verifying the signature:

```bash showLineNumbers={false}
echo "<payload-segment>" | base64 -d
```

Inspect the following claims:

| Claim         | What to check                                                                                  |
| :------------ | :--------------------------------------------------------------------------------------------- |
| `channel_id`  | Matches `BIGCOMMERCE_CHANNEL_ID` in the environment that generated the URL.                    |
| `redirect_to` | Points at the same domain the shopper is on, not another store or environment domain.          |
| `eat`         | Expires 30 seconds after `iat`. A much larger gap points to clock skew on the Catalyst server. |