SEON Shopify Agent for Custom Storefronts
Updated on 21.10.25
1 minute to read
Copy link
Overview
This guide explains how to integrate the SEON browser agent into custom Shopify storefronts (e.g., Hydrogen, Next.js, Remix, Nuxt) and how it behaves in theme-based storefronts, where it’s injected automatically by the SEON app.
The SEON browser agent gathers device intelligence and produces a Base64-encoded session payload that you can attach to orders, checkouts, or identity events.
- Theme storefronts (Online Store 2.0 / Liquid)
The SEON app auto-injects the agent. Nothing to install manually. - Custom storefronts (Hydrogen, headless, etc.)
You must manually embed the agent script. It handles fingerprint generation, cart token tracking, and session payload creation.
Integration
Add this script to your storefront (for example in your root layout or app.tsx/index.html).
It will initialize automatically once the DOM is ready.
(function () {
'use strict';
// Prevent multiple initializations
if (window.SEON_AGENT) {
return;
}
// Default configuration
const defaultConfig = {
environment: 'production',
cartEndpoint: '/cart.json',
debug: false,
autoInit: true,
host: 'getdeviceinf.com',
audioFingerprint: true,
canvasFingerprint: true,
webglFingerprint: true,
};
// Merge user config with defaults
const config = { ...defaultConfig, ...(window.SEON_CONFIG || {}) };
// Storage keys
const STORAGE_KEYS = {
VISITOR_ID: 'seon_visitor_id',
CART_TOKEN: 'seon_cart_token',
};
// State management
let isInitialized = false;
let isSeonLoaded = false;
let initializationPromise = null;
/**
* Configure SEON with session token
*/
async function configureSeon(token) {
if (!window.seon) {
throw new Error('SEON agent not loaded');
}
return new Promise((resolve, reject) => {
window.seon.config({
session_id: token,
host: config.host,
audio_fingerprint: config.audioFingerprint,
canvas_fingerprint: config.canvasFingerprint,
webgl_fingerprint: config.webglFingerprint,
onSuccess: message => {
resolve(message);
},
onError: message => {
reject(new Error(message));
},
});
});
}
/**
* Generate and store SEON payload
*/
async function generatePayload(token) {
if (!token) {
throw new Error('Token is required');
}
await configureSeon(token);
return new Promise((resolve, reject) => {
window.seon.getBase64Session(async data => {
if (data) {
localStorage.setItem(STORAGE_KEYS.VISITOR_ID, data);
resolve(data);
} else {
reject(new Error('Failed to retrieve session data'));
}
});
});
}
/**
* Fetch cart token from Shopify
*/
async function fetchCartToken() {
try {
const response = await fetch(config.cartEndpoint, {
priority: 'low',
headers: {
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const cart = await response.json();
return cart.token;
} catch (error) {
throw error;
}
}
/**
* Check and update session if needed
*/
async function checkAndUpdateSession(initialToken = null) {
let cartToken = initialToken;
// Try to get cart token from Shopify
try {
cartToken = await fetchCartToken();
} catch (error) {
// If cart fetch fails and no initial token, generate one
if (!cartToken) {
cartToken = crypto.randomUUID();
}
}
const storedToken = localStorage.getItem(STORAGE_KEYS.CART_TOKEN);
const visitorId = localStorage.getItem(STORAGE_KEYS.VISITOR_ID);
// Update session if token changed or no visitor ID exists
if (storedToken !== cartToken || !visitorId) {
if (storedToken !== cartToken) {
localStorage.setItem(STORAGE_KEYS.CART_TOKEN, cartToken);
localStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
}
await generatePayload(cartToken);
}
}
/**
* Load SEON agent script
*/
function loadSeonAgent() {
return new Promise((resolve, reject) => {
if (isSeonLoaded) {
resolve();
return;
}
const script = document.createElement('script');
script.type = 'text/javascript';
script.fetchPriority = 'low';
script.defer = true;
script.src = 'https://cdn.getdeviceinf.com/js/v5/agent.js';
script.onload = () => {
isSeonLoaded = true;
resolve();
};
script.onerror = () => {
reject(new Error('Failed to load SEON agent'));
};
document.head.appendChild(script);
});
}
/**
* Initialize SEON fraud detection
*/
async function initialize() {
if (isInitialized) {
return initializationPromise;
}
if (initializationPromise) {
return initializationPromise;
}
initializationPromise = (async () => {
try {
// Load SEON agent
await loadSeonAgent();
// Wait a bit for the agent to be ready
await new Promise(resolve => setTimeout(resolve, 100));
// Check and update session
await checkAndUpdateSession();
isInitialized = true;
} catch (error) {
throw error;
}
})();
return initializationPromise;
}
/**
* Public API
*/
window.SEON_AGENT = {
/**
* Initialize SEON
*/
init: initialize,
/**
* Get current visitor ID
*/
getVisitorId: () => {
return localStorage.getItem(STORAGE_KEYS.VISITOR_ID);
},
/**
* Get current cart token
*/
getCartToken: () => {
return localStorage.getItem(STORAGE_KEYS.CART_TOKEN);
},
/**
* Manually update session with a new token
*/
updateSession: async (token) => {
if (!isInitialized) {
throw new Error('SEON not initialized. Call init() first.');
}
await checkAndUpdateSession(token);
},
/**
* Clear stored session data
*/
clearSession: () => {
localStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
localStorage.removeItem(STORAGE_KEYS.CART_TOKEN);
},
/**
* Check if SEON is initialized
*/
isInitialized: () => isInitialized,
/**
* Get current configuration
*/
getConfig: () => ({ ...config }),
};
// Auto-initialize if configured
if (config.autoInit) {
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(initialize, 1000);
});
} else {
setTimeout(initialize, 1000);
}
}
})();
How It Works
- Script loading: It lazily loads
https://cdn.getdeviceinf.com/js/v5/agent.js
and initializes SEON. - Session management: It fetches the current Shopify cart token (or generates one if missing) and uses it as the session identifier.
- Payload storage: A Base64-encoded fingerprint is stored in
localStorage
underseon_visitor_id
. - Automatic behavior: If
autoInit
is true, the agent initializes after DOM load without any manual trigger.