A headless store separates your ecommerce platform from the storefront your customers see. Your platform still handles products, carts, orders and customer accounts, but the front end is a separate application you build and control.
If your store runs on a headless ecommerce site, you can still integrate it with Lootly. The backend connection is unchanged. What changes is that you add the loyalty widget to your front end yourself, and tell it which customer is signed in.
Please note: this guide involves adding code to your storefront, so you will likely need a developer.
In short:
What keeps working, and what you build
Feature | On a headless storefront |
Order and points sync | Automatic |
Refund and cancellation handling | Automatic |
Points for creating an account | Automatic |
Referral tracking and attribution | Automatic |
VIP tier progression | Automatic |
The loyalty widget | You add it |
Signing customers into the widget | You add it |
In-cart reward redemption | Not available at this time |
Checkout points reminder | Not available at this time |
Everything marked automatic runs on your ecommerce platform's backend, so it keeps working no matter what front end sits on top. You do not need to rebuild any of it.
Step 1. Integrate Lootly with your backend ecommerce platform
In a headless setup the backend is usually still one of the standard ecommerce platforms. Connect it to Lootly exactly as you would on a non-headless store, using the setup guide for your platform:
Custom API, if your backend is not one of the above
Install and configure the integration as normal, then place a test order and confirm it appears in your Lootly dashboard before moving on. If orders are not arriving, stop here and fix that first. Nothing in the following steps will work without it.
Magento merchants: the Order Status setting has no default value. Until you choose one, no orders are sent to Lootly, no points are earned and no referral ever pays out. Nothing looks broken, so this is easy to miss.
Set it under Stores → Configuration → Lootly Loyalty & Referrals → General API Settings → Order Status. The options are Processing, Invoiced, Shipped, Complete and Custom Event.
Step 2. Add the widget to your front end
On a standard storefront our integration injects the loyalty widget on every page for you. On a headless storefront, that injection never happens, so you add it yourself wherever you want the widget launcher to appear.
Render this container:
```html
<div id="lootly-widget"
class="lootly-init"
style="display: none"
data-shop-domain="https://yourstore.com/"
data-shop-id="COMPUTED_SHOP_ID"
data-customer-id="CUSTOMER_ID_OR_EMPTY"
data-customer-signature="COMPUTED_SIGNATURE_OR_EMPTY">
</div>
```
Then load the widget script once per page:
```html
<script src="https://app.lootly.io/js/integrations/common/script.js?shop=https://yourstore.com/"></script>
```
Use the `common` script on a headless build no matter which platform you are on. The platform-specific scripts only add cart and product helpers that depend on that platform's own storefront, which a headless front end does not have.
Attributes the widget reads
Attribute | Required | Value |
`data-shop-domain` | Yes | Your store's base URL |
`data-shop-id` | Yes | Generated from your API secret. See step 3 |
`data-customer-id` | When signed in | The customer's ID in your ecommerce platform. Leave empty when nobody is signed in |
`data-customer-signature` | When signed in | Generated from your API secret. See step 3 |
`data-customer-email` | Optional | The customer's email address. Can be used instead of the customer ID |
`data-hide-variable-rewards` | Optional | Set to `1` to hide variable point rewards from the widget |
The container is matched by `#lootly-widget`, or by `[data-lootly-widget].lootly-init`, or by `.lootly-init[data-api-key]`.
Using `id="lootly-widget"` is the simplest option.
The widget renders inside an iframe, so it looks and behaves identically regardless of the framework your front end is built in. React, Vue, Svelte, Next.js and plain JavaScript all work the same way.
Match your store URL exactly. `data-shop-domain` is used to build `data-shop-id`, so both must be generated from the same string, trailing slash included. A mismatch means the widget loads but does not recognise your store.
Step 3. Sign your customer into the widget
This is the step most headless builds miss. Without it the widget loads, but every visitor sees it signed out. Nobody can view their points balance, rewards or referral link, even when they are signed into your store.
The widget identifies a customer using two values you generate from your API secret:
```
shop_id = md5( shop_domain + api_secret )
customer_signature = md5( customer_id + api_secret )
```
`customer_id` is the customer's ID in your ecommerce platform, the same ID your platform sends to Lootly with their orders. Your API key and API secret are in your Lootly dashboard, under your platform integration settings.
Generate both values on your server, then pass them through to your front end:
```js
// server side only
const shopId = md5(SHOP_DOMAIN + LOOTLY_API_SECRET);
const signature = md5(platformCustomerId + LOOTLY_API_SECRET);
// send shopId and signature to the front end,
// then render the container from step 2
```
Your API secret must stay on your server. Never include it in your front-end bundle and never expose it through a public endpoint. Anyone who has your API secret can generate a valid signature for any of your customers and read their loyalty account.
Expose only the finished `shop_id` and `customer_signature` values, and only for the customer who is currently signed in.
When no customer is signed in, leave both `data-customer-id` and `data-customer-signature` empty. The widget then shows its signed-out state, which is the correct behaviour and lets visitors join your program.
An incorrect signature fails silently. Lootly does not return an error for a signature that does not match. The widget loads normally and simply shows no customer data. If your widget always looks signed out, the signature is the first thing to check.
Step 4. Refresh the widget when customers sign in and out
On a standard storefront every page load is a fresh request from the server, so the widget always receives the current customer. A single-page application does not reload between pages, so you have to keep the widget in step yourself.
The widget will not rebuild itself while its iframe is still on the page. You must remove the iframe first, update the attributes, then call the init function:
```js
function refreshLootlyWidget(customerId, signature) {
const widget = document.querySelector('#lootly-widget');
if (!widget) return;
// 1. remove the existing iframe, or init will do nothing
const iframe = widget.querySelector('#lootly_iframe');
if (iframe) iframe.remove();
// 2. update the customer attributes
// (pass empty strings when signing out)
widget.setAttribute('data-customer-id', customerId || '');
widget.setAttribute('data-customer-signature', signature || '');
// 3. re-initialise, passing your Lootly app URL
if (typeof window.lootlyWidgetInit === 'function') {
window.lootlyWidgetInit('https://app.lootly.io');
}
}
```
Call this function whenever a customer signs in, signs out, or creates an account.
Pass the app URL. `window.lootlyWidgetInit` requires your Lootly app URL as its argument and will throw an error if you call it without one. Use the same host you load the script from.
Do not skip the sign-out case. If you only refresh the widget on sign-in, it keeps showing the previous customer's points and rewards after they sign out. On a shared or public computer, the next person sees that account.
Step 5. Test your integration
Open your storefront signed out. The widget appears and invites the visitor to sign in or join
Sign in. The widget shows that customer's points balance without a page refresh
Place a test order and let it reach the status your integration syncs on. The points appear in that customer's Lootly account
Sign out. The widget returns to its signed-out state immediately
Sign in as a different customer. The widget shows the second customer's balance, never the first customer's
Open a referral link in a private window and complete a test order. The referral is attributed and the sender is rewarded
Test 5 is the one most often skipped, and it is the one your customers are most likely to notice if it is wrong.
Platform notes
The widget behaves identically on every platform. What differs is which customer ID you hash in step 3, and a few platform-specific settings.
Platform | Use this as 'customer id' |
Shopify | The Shopify customer ID |
Woocommerce | The WordPress user ID for that customer |
Bigcommerce | The BigCommerce customer ID |
Magento 2 | The Magento customer entity ID |
Maropost (former Neto) | The Neto customer username or ID |
Custom API | The customer ID you send to Lootly with your orders |
Magento
Keep the Lootly extension installed. It handles order sync, refunds, account creation and referral attribution through Magento's backend events, and all of that continues to work on a headless storefront. Only the storefront display needs replacing.
Your API key, API secret and Order Status can each be set at default, website or store view level. If you run several markets from one Magento instance, you can point each website at its own Lootly account.
Neto and Custom API
On these platforms coupons are not created by Lootly inside your store. You upload coupon codes to Lootly, or your own endpoint issues them. Any restriction on how a coupon can be used has to be configured on the coupon itself in your platform's admin, because Lootly cannot enforce it at your checkout.
Running several stores or markets
Connect each store to its own Lootly account so customers and point balances stay separate. That usually matches how your ecommerce platform already separates customer accounts between storefronts. Customers who register on more than one of your stores will have a separate balance on each.
What is not available on a headless store
In-cart reward redemption and the checkout points reminder are not available on headless storefronts.
Both are rendered by our integration directly into your platform's own cart and checkout templates. A headless front end does not load those templates, so there is nothing for them to attach to.
Your customers can still redeem rewards in the widget and receive a discount code to apply at checkout. This is the standard redemption flow and it works fully on a headless store.
If you want rewards applied to the cart automatically, that has to be built into your front end against our widget API, applying the resulting discount code through your platform's own cart functions. Contact support and we will point your developer at the right endpoints.
Troubleshooting
The widget appears, but always shows as signed out. - The signature is not matching. Confirm you are hashing the customer's ID from your ecommerce platform and not their Lootly customer ID, that the API secret has no leading or trailing whitespace, and that the secret belongs to the same store you are connecting.
The widget does not appear at all. - Check that your container matches one of the selectors in step 2, that the script loaded without a network or content security policy error, and that `data-shop-id` and `data-shop-domain` were built from the identical store URL string.
The widget still shows the previous customer after sign-out. - The iframe is not being removed before re-initialising. See step 4. Removing `#lootly_iframe` is required, updating the attributes alone is not enough.
Points are not awarded for orders. - This is a backend integration problem, not a headless one. Check that orders reach your Lootly dashboard. On Magento, confirm the Order Status setting has been chosen and that your test order actually reached that status.
The widget is blocked by your content security policy. - Allow `app.lootly.io` in your `script-src`, `connect-src` and `frame-src` directives.
Need help?
Email support@lootly.io with your store URL, your ecommerce platform, and where on your site the widget is mounted.
If customers are not being signed in, tell us whether `data-customer-signature` is generated on your server or in the browser. That is the most common cause and it saves a round trip.
