Headless SDK
Add the full Anchor loyalty widget to any headless storefront — Hydrogen, Next.js, Gatsby, Vue, or custom builds.
#Overview
The Anchor Loyalty Headless SDK allows you to add the loyalty widget to any storefront that doesn't use Shopify's Online Store channel — including Hydrogen, Next.js, Gatsby, Vue, or any custom frontend.
The SDK renders the exact same widget as the embedded version — same UI, same features, lightweight footprint. No custom UI development required.
infoHow it works
The SDK handles all communication with Anchor securely. You only need to provide your API key during initialization — the SDK takes care of the rest.
- checkAnchor Loyalty app installed on your Shopify store
- checkPRO plan subscription
- checkAPI key generated from the Anchor Loyalty dashboard
#Authentication
All headless SDK API calls require an API key.
- 1Generate your API key — from the Anchor Loyalty dashboard (Headless SDK section).
- 2Save the key securely — it is displayed only once. If you lose it, use "Regenerate Key" to create a new one (this invalidates the old key).
- 3Include apiKey in your SDK initialization — call (see Quick Start below).
- 4Revoke and regenerate — if a key is compromised, revoke it from the dashboard. A new key can be generated immediately.
- 5Generate customer tokens — Your backend must sign a customer token using HMAC-SHA256 with your signing secret. The signing secret is shown alongside the API key when generated — store it securely in your backend environment variables. Required for all authenticated requests (reads and redemptions). See Security section.
#Security — Customer Token
The API key authenticates your store, but it's visible in your page source. To prevent unauthorized access, all authenticated headless requests require a customer token — an HMAC-signed proof that your backend verified the customer.
#How it works
- 1Customer logs in — on your storefront.
- 2Your backend generates a signed token — using HMAC-SHA256 with your signing secret (never send the signing secret to the browser).
- 3Frontend passes the token — to the SDK via the customerToken parameter.
- 4Anchor verifies the signature — before processing any request.
#Generating the token (backend)
const crypto = require('crypto');
const ts = Math.floor(Date.now() / 1000);
const hmac = crypto.createHmac('sha256', SIGNING_SECRET)
.update(`${customerId}.${ts}`).digest('hex');
const customerToken = `${customerId}.${ts}.${hmac}`;// In a Hydrogen loader (e.g., app/routes/($locale)._index.tsx)
import crypto from 'crypto';
export async function loader({ context }: LoaderFunctionArgs) {
const isLoggedIn = await context.customerAccount.isLoggedIn();
let customerToken = null;
if (isLoggedIn) {
const { data } = await context.customerAccount.query(`
query { customer { id } }
`);
const customerId = data.customer.id.replace('gid://shopify/Customer/', '');
const ts = Math.floor(Date.now() / 1000);
const hmac = crypto.createHmac('sha256', context.env.ANCHOR_SIGNING_SECRET)
.update(`${customerId}.${ts}`).digest('hex');
customerToken = `${customerId}.${ts}.${hmac}`;
}
return json({ customerToken });
}Token expires after 5 minutes. Regenerate on each page load.
#When is it required?
| Endpoint | Token | Note |
|---|---|---|
| POST /api/redeem | REQUIRED | Returns 403 without valid token |
| GET /api/customer | REQUIRED | Returns 403 without valid token |
| GET /api/codes | REQUIRED | Returns 403 without valid token |
#Quick Start
#HTML / Custom Storefront
Recommended — only API key needed:
<script src="https://anchorloyalty.app/api/sdk.js?key=YOUR_API_KEY"></script>
<script>
AnchorLoyaltySDK.init({
apiKey: 'YOUR_API_KEY',
customerId: '12345', // Shopify Customer ID (numeric)
customerToken: 'TOKEN_FROM_YOUR_BACKEND', // HMAC-signed token
});
</script>When loading via ?key=, the SDK automatically resolves your store — no shop parameter needed. Your myshopify.com domain is never exposed in page source.
Alternative — with explicit shop domain:
<script src="https://anchorloyalty.app/api/sdk.js?shop=YOUR-STORE.myshopify.com"></script>
<script>
AnchorLoyaltySDK.init({
shop: 'YOUR-STORE.myshopify.com',
apiKey: 'YOUR_API_KEY',
customerId: '12345',
});
</script>#Hydrogen / React
Create a LoyaltyWidget component that loads the SDK and initializes it with the authenticated customer:
import { useEffect } from 'react';
import { useCustomer } from '~/hooks/useCustomer';
const API_KEY = import.meta.env.PUBLIC_ANCHOR_LOYALTY_API_KEY;
export function LoyaltyWidget() {
const customer = useCustomer();
useEffect(() => {
if (!API_KEY) {
console.error('Missing PUBLIC_ANCHOR_LOYALTY_API_KEY env var');
return;
}
const script = document.createElement('script');
script.src = `https://anchorloyalty.app/api/sdk.js?key=${API_KEY}`;
script.async = true;
script.onload = () => {
window.AnchorLoyaltySDK?.init({
apiKey: API_KEY,
customerId: customer?.id?.replace('gid://shopify/Customer/', '') || null,
customerToken: customerToken, // Pass token from your backend
customerName: customer?.firstName || '',
locale: 'en',
});
};
document.body.appendChild(script);
return () => {
window.AnchorLoyaltySDK?.destroy();
script.remove();
};
}, [customer]);
return null;
}#Next.js
For Next.js, use the built-in next/script component with the onReady callback. This ensures the SDK re-initializes on SPA route transitions:
'use client';
import Script from 'next/script';
const API_KEY = process.env.NEXT_PUBLIC_ANCHOR_LOYALTY_API_KEY;
export function LoyaltyWidget({ customerId, customerName, customerToken }: {
customerId: string | null;
customerName?: string;
customerToken?: string;
}) {
if (!API_KEY) {
console.error('Missing NEXT_PUBLIC_ANCHOR_LOYALTY_API_KEY env var');
return null;
}
return (
<Script
src={`https://anchorloyalty.app/api/sdk.js?key=${API_KEY}`}
strategy="lazyOnload"
onReady={() => {
window.AnchorLoyaltySDK?.init({
apiKey: API_KEY,
customerId,
customerName,
customerToken,
});
}}
/>
);
}Why next/script? It handles deduplication (loads only once across navigations), supports lazyOnload for zero performance impact, and onReady re-fires on every mount — perfect for SPA route changes.
#Getting the Customer ID
The customerId must be the Shopify numeric Customer ID (not the GID).
#From Hydrogen (Customer Account API)
// In a Hydrogen loader:
const isLoggedIn = await context.customerAccount.isLoggedIn();
if (isLoggedIn) {
const { data } = await context.customerAccount.query(`
query { customer { id firstName lastName } }
`);
// data.customer.id = "gid://shopify/Customer/12345"
const numericId = data.customer.id.replace('gid://shopify/Customer/', '');
// numericId = "12345"
}#From Storefront API
query {
customer(customerAccessToken: "TOKEN") {
id # Returns "gid://shopify/Customer/12345"
}
}Extract the numeric part: id.split('/').pop() → "12345"
#From Custom Backend (Admin API)
GET /admin/api/2026-01/customers.json?email=user@example.comUse the id field from the response (already numeric).
#Guest Users (Not Logged In)
Pass null or omit customerId:
AnchorLoyaltySDK.init({
apiKey: 'YOUR_API_KEY',
customerId: null, // Guest mode — shows sign-in prompt
});#All Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | API key from your Anchor Loyalty dashboard |
| shop | string | auto-resolved | Your *.myshopify.com domain. Auto-resolved from API key when SDK is loaded via ?key= |
| customerId | string | null | Shopify Customer ID (numeric). Omit or pass null for guest mode |
| customerToken | string | null | HMAC-signed customer verification token. Required for all authenticated requests (reads and redemptions). Generated by your backend: HMAC-SHA256(customerId.timestamp, signingSecret). See Security section below. |
| customerName | string | '' | Customer's first name for welcome message |
| locale | string | 'en' | Language code. 32 locales built-in (see below) |
| position | string | 'bottom-right' | 'bottom-right' or 'bottom-left' |
| primaryColor | string | '#4A56E2' | Primary brand color (hex) |
| backgroundColor | string | '#FFFFFF' | Widget background color |
| textColor | string | '#1F2937' | Widget text color |
| launcherStyle | string | 'bubble' | 'bubble', 'bubble-text', 'icon-only', 'pill', 'square' |
| launcherGradient | string | 'solid' | 'solid', 'sunset', 'ocean', 'forest', 'candy', 'midnight' |
| launcherShadow | string | 'medium' | 'none', 'subtle', 'medium', 'strong', 'glow' |
| launcherAnimation | string | 'pulse' | 'none', 'pulse', 'bounce', 'shake', 'glow' |
| launcherTextType | string | 'static' | 'static' (shows 'Rewards') or 'dynamic' (shows points) |
| animationDelay | number | 5 | Seconds before first animation |
| animationRepeat | number | 30 | Seconds between repeats (0 = no repeat) |
| animateOnScroll | boolean | true | Animate when user scrolls 25% down |
| bannerImage | string | '' | URL to hero banner image |
| bannerLink | string | '' | URL the banner links to |
| bannerLinkNewTab | boolean | false | Open banner link in new tab |
| showRewards | boolean | true | Show rewards tab |
| showReferral | boolean | true | Show referral tab |
| showGuestMessage | boolean | true | Show sign-in prompt for guests |
| accountUrl | string | '/account' | "View Account" link destination |
| loginUrl | string | '/account/login' | "Sign In" link for guests |
| registerUrl | string | '/account/register' | "Create Account" link for guests |
| translations | object | {} | Override any default translation key |
#Locale Support
32 built-in locales — set locale to auto-select:
AnchorLoyaltySDK.init({ apiKey: '...', locale: 'de' }); // DeutscharالعربيةcsČeštinadaDanskdeDeutschelΕλληνικάenEnglishesEspañolfiSuomifrFrançaisfr-CAFrançais (CA)heעבריתhiहिन्दीhuMagyaridBahasa IndonesiaitItalianoja日本語ko한국어msBahasa MelayunlNederlandsnoNorskplPolskipt-BRPortuguês (BR)pt-PTPortuguês (PT)roRomânăruРусскийsvSvenskathไทยtrTürkçeukУкраїнськаviTiếng Việtzh-CN简体中文zh-TW繁體中文RTL support: Arabic (ar) and Hebrew (he) locales automatically switch the widget layout to right-to-left, including text direction, icon positions, and animations.
Override individual keys:
AnchorLoyaltySDK.init({
apiKey: '...',
locale: 'fr',
translations: { title: 'Mon Programme Fidélité' },
});#Methods
AnchorLoyaltySDK.init(options)
Initialize the widget. Call once after the script loads.
AnchorLoyaltySDK.destroy()
Remove the widget completely. Cleans up:
- - Widget DOM element
- - Injected CSS and JS
- - All
windowglobals (anchorLoyaltyConfig,anchorLoyaltyTranslations,anchorLoyalty)
Safe for SPA route transitions.
AnchorLoyaltySDK.update(newOptions)
Update options and re-initialize (e.g., after login/logout). Preserves custom translations.
// After user logs in:
AnchorLoyaltySDK.update({
customerId: '12345',
customerName: 'Jane',
});#Architecture
Your Headless Storefront Anchor Loyalty API
(Hydrogen, Next.js, etc.) (anchorloyalty.app)
| |
| 1. Load SDK |
| GET /api/sdk.js?key=ak_xxx |
|------------------------------------------->|
| <-- JS (widget + CSS + 32 locales) |
| |
| 2. Fetch customer data (automatic) |
| GET /api/customer |
|------------------------------------------->|
| <-- JSON (points, rewards, tier, etc.) |
| |
| 3. Redeem reward |
| POST /api/redeem |
|------------------------------------------->|
| <-- JSON (discount code) |
| |
| 4. Redeem reward (with customer token) |
| POST /api/redeem |
| Headers: X-Anchor-Customer-Token: |
| {customerId}.{ts}.{hmac} |
|------------------------------------------->|
| <-- JSON (discount code) |#Troubleshooting
| Issue | Solution |
|---|---|
| SDK returns 403 | Verify API key is correct and not revoked. Check PRO plan is active. |
| Widget doesn't appear | Check browser console for errors |
| "Customer not logged in" | Verify customerId is the numeric Shopify ID, not the GID |
| Wrong language | Set locale parameter (e.g., 'de', 'fr', 'ja') |
| Widget overlaps UI | Adjust position or use custom CSS |
| CORS errors | Verify you're loading SDK from anchorloyalty.app, not from a local file |
| Redemption returns 403 "Invalid or expired customer token" | Verify your backend generates the token correctly. Check timestamp is within 5 minutes. Ensure customerId in token matches the request. |
| "API key required" on redemption | Include the API key via X-Anchor-Api-Key header or api_key query param alongside the customer token. |
| "Signing secret not configured" | Regenerate your API key from the dashboard. Both API key and signing secret will be shown once — store the signing secret in your backend environment variables. |
Ready to add loyalty to your headless store?
Install Anchor, upgrade to Pro, and drop the SDK into your storefront. Full widget experience in minutes.