The LoyaltyLion Headless API is easy to use with Shopify Hydrogen, and lets you build customised loyalty experiences that support server-side rendering.

We maintain a reference implementation for Hydrogen which you can use as a starting point for your own implementation.

Calling the Headless API

Use the @loyaltylion/headless-api-client library

We provide a lightweight (~4KB gzipped) TypeScript library, @loyaltylion/headless-api-client, for interacting with the LoyaltyLion Headless API, which is the fastest way to integrate with your Hydrogen storefront.

Using this library, you can quickly create a fully typed Headless API client and use it to call all Headless API endpoints. This is the option used in our reference implementation.

The client automatically handles authentication and other subtle details such as encoding Shopify GIDs, and is fully typed out of the box.

import { createHeadlessApiClient } from "@loyaltylion/headless-api-client";

const client = createHeadlessApiClient({
  siteId: parseInt(process.env.LOYALTYLION_SITE_ID),
  apiKey: process.env.LOYALTYLION_API_KEY,
  channel: "web",
});

// retrieve the full site configuration
const configuration = await client.getConfiguration();

Use our TypeScript typings

If you’d prefer to manage your own HTTP requests, you can still use our @loyaltylion/headless-api-client library, as it exports a complete set of TypeScript types for use with the Headless API.

import type {
  CustomersInitializeSessionRequestBody,
  CustomersInitializeSessionResponseBody,
} from "@loyaltylion/headless-api-client";

const res = await fetch(
  "https://api.loyaltylion.com/headless/2025-06/${siteId}/customers/${merchantId}/sessions",
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "X-LoyaltyLion-Channel": "web",
    },
    body: JSON.stringify({
      customer: { email: "alice@example.com" },
    } satisfies CustomersInitializeSessionRequestBody),
  }
);

if (!res.ok) throw new Error("API request failed");

const data: CustomersInitializeSessionResponseBody = await res.json();

Loading LoyaltyLion configuration and customer data

The most straightforward way to load LoyaltyLion data and pass it down to your components for rendering is to load it as deferred data in your root loader (root.tsx).

Sending LoyaltyLion data down as a deferred load ensures that the rest of your page will never be blocked by LoyaltyLion. Using Suspense and Await you can wait until the data has loaded before rendering the relevant components.

The exact data you need to load depends on whether a customer is logged in or not. In our reference implementation, we have a fetchLoyaltylionData function which checks if a customer is logged in, and then calls the appropriate endpoint to load the relevant data.

When no one is logged in

When no one is logged in, the only relevant data you need to load is the Site Configuration, via the Get Configuration endpoint.

This includes the full set of configuration for your site and associated program in LoyaltyLion, such as the program name, tiers, rules, and rewards. You could use this to render general information about the program anywhere on the store, or on a dedicated /rewards page.

If you’re using tiers, the rules and rewards in the configuration will contain multiple variants, which reflect the rule and reward’s configuration for each tier, e.g. if it’s enabled or provides more points for a particular tier.

It’s up to you how you want to render these variants. In the official LoyaltyLion JS SDK, we always render the first enabled variant, but you could, for example, render all variants, e.g. to show prospective customers the difference between each tier.

Customer objects will have available_rules and available_rewards, which are reflect the rules and rewards that are available to the customer, based on their tier. Each rule and reward will have a single variant (based on the tier), and a context object, which can be used to determine if the customer can actually interact with the rule or reward.

When a customer is logged in

Calls to Get Customer and Initialize Session will always return the full site configuration in the response, in addition to the customer, so you never need to call Get Configuration separately if you’re using either of these endpoints.

If a customer is logged in, you’ll instead want to instead load data specific to the logged in customer. This can be done using the Get Customer and Initialize Session endpoints.

Note that the returned customer object may not be a member of the program. You need to check customer.state to determine if they are enrolled, a guest (non-member), or blocked from the program.

For Hydrogen, we recommend using the Initialize Session endpoint and calling it in the root loader. This will trigger a new call any time the page is reloaded from scratch, or when any mutation occurs (e.g. the cart changes).

If you need to retrieve the customer in other loaders, we recommend using the Get Customer endpoint instead. This endpoint is faster because it never create or update the customer in LoyaltyLion, which is fine if you’re calling Initialize Session in the root loader (i.e. it’s guaranteed to have been called at least once when a customer logged in).