GTM Integration
Load the Sophi Web SDK via Google Tag Manager with consent-aware script injection and app-side initialization.
For @usesophi/sophi-web-object (commerce tracking without the widget), use Web Object GTM integration.
Use this setup when the Sophi Web SDK (chat widget) is loaded by Google Tag Manager (GTM), not from npm runtime imports.
For most teams, the recommended GTM setup is load on all pages + consent-aware storage upgrade. See Cookie Consent Management.
Browser global
When loaded by script, the SDK is exposed as:
window.SophiWebSDK.SophiWidget;Script URL (current)
Use this script URL in GTM-injected script element:
https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.jsCookie consent management (recommended)
This is the default model we recommend: load Sophi on every page, keep it memory-only before consent, then upgrade to persistent storage when the visitor accepts cookies.
1. Load the SDK on all pages
Use a GTM tag that injects the SDK on page load regardless of consent:
<script>
(function () {
if (window.SophiWebSDK && window.SophiWebSDK.SophiWidget) return;
var src = "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js";
var script = document.createElement("script");
script.src = src;
script.async = true;
script.onload = function () {
window.dispatchEvent(new CustomEvent("sophi:sdk_loaded"));
window.dispatchEvent(new CustomEvent("sophi:init"));
};
document.head.appendChild(script);
})();
</script>2. Initialize memory-only
Initialize with storageConsent: false so no cookies are written yet:
const SophiWidgetClass = await waitForSophiSDK(5000);
const widget = new SophiWidgetClass();
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
userId,
userName,
environment: "production",
storageConsent: false, // memory-only until consent
});3. Upgrade on the consent-granted event
Add a small Custom HTML tag (or app-side listener) tied to your consent-updated event that upgrades the live widget in place:
<script>
if (window.sophiWidget) {
// Widget Launcher
window.sophiWidget.setConsent(true);
}
</script>If you manage the SophiWidget instance yourself, call
widget.setStorageConsent(true) from your app code instead.
Strict consent-gated GTM trigger configuration
Tag
- Tag Type:
Custom HTML - Purpose: Load SDK only after functional consent is granted (strict policy)
Triggers
Attach the same Custom HTML tag to both triggers:
- Consent on Granted
- Page onload
This covers:
- consent already granted before page load
- consent granted during current session
Custom HTML behavior
The GTM tag should:
- Read and parse
cookie_consentcookie as JSON - Check
parsed.functional === true - If true, inject script URL (above) once
- On script load, dispatch:
sophi:sdk_loadedsophi:init
<script>
(function () {
function getCookie(name) {
var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
return match ? decodeURIComponent(match[2]) : null;
}
function hasFunctionalConsent() {
try {
var raw = getCookie("cookie_consent");
if (!raw) return false;
var parsed = JSON.parse(raw);
return parsed && parsed.functional === true;
} catch (e) {
return false;
}
}
if (!hasFunctionalConsent()) return;
if (window.SophiWebSDK && window.SophiWebSDK.SophiWidget) return;
var src = "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js";
var script = document.createElement("script");
script.src = src;
script.async = true;
script.onload = function () {
window.dispatchEvent(new CustomEvent("sophi:sdk_loaded"));
window.dispatchEvent(new CustomEvent("sophi:init"));
};
document.head.appendChild(script);
})();
</script>App-side wait flow
Initialize the widget only when:
- functional consent exists
waitForSophiSDK(5000)resolves- readiness is confirmed by either:
sophi:initwindow eventwindow.SophiWebSDK.SophiWidgetpresence
const SophiWidgetClass = await waitForSophiSDK(5000);
const widget = new SophiWidgetClass();
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
userId,
userName,
environment: "production",
});Keep configuration and event listeners in application code.
GTM should only handle consent-gated script loading.
Troubleshooting
Script does not load from GTM
- Confirm the Custom HTML tag fires on the expected consent triggers.
- Confirm your consent condition resolves to granted state.
- Open GTM Preview mode and verify the tag actually fires on page load and consent change.
window.SophiWebSDK is undefined
- SDK script has not loaded yet, or load was blocked by consent/trigger logic.
- Confirm jsDelivr URL is valid and reachable.
- Delay widget init until your wait strategy succeeds (
sophi:initevent orwaitForSophiSDK).
waitForSophiSDK timeout
- Increase timeout if consent workflow is delayed.
- Ensure consent update event is pushed when user accepts.
- Ensure GTM trigger for consent-updated path exists (not only initial page load).
Init runs before container exists
- Make sure
#sophi-widget-containerexists in DOM before callingwidget.init. - In React/Angular, initialize after component mount/view init.
Consent granted after page load, but widget still missing
- Attach the same SDK load tag to both:
- initial granted-consent trigger
- mid-session consent-granted trigger
- Confirm the mid-session trigger is tied to your consent update event.
Duplicate init / repeated listeners
- Keep a single widget instance reference and guard against re-init.
- On teardown, call
widget.destroy()and clear references. - Register listeners once per widget instance to avoid duplicate callbacks.
Optional helper example (waitForSophiSDK)
This is a reference example only to help teams implementing GTM-based loading. You do not have to copy this exact helper; any equivalent wait strategy is fine.
const SOPHI_INIT_EVENT = "sophi:init";
const POLL_INTERVAL_MS = 150;
export function waitForSophiSDK(timeoutMs: number): Promise<new () => any> {
return new Promise((resolve, reject) => {
if (typeof window === "undefined") {
reject(new Error("waitForSophiSDK: window is undefined"));
return;
}
const w = window as Window & { SophiWebSDK?: { SophiWidget: new () => any } };
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let pollId: ReturnType<typeof setInterval> | null = null;
function cleanup() {
window.removeEventListener(SOPHI_INIT_EVENT, onInit);
if (timeoutId) clearTimeout(timeoutId);
if (pollId) clearInterval(pollId);
timeoutId = null;
pollId = null;
}
function tryResolve() {
if (w.SophiWebSDK?.SophiWidget) {
cleanup();
resolve(w.SophiWebSDK.SophiWidget);
}
}
function onInit() {
tryResolve();
}
// Already available
tryResolve();
if (w.SophiWebSDK?.SophiWidget) return;
// GTM might dispatch `sophi:init`
window.addEventListener(SOPHI_INIT_EVENT, onInit);
// Or set `window.SophiWebSDK` without the event
pollId = setInterval(tryResolve, POLL_INTERVAL_MS);
timeoutId = setTimeout(() => {
cleanup();
reject(new Error(`Sophi SDK did not load within ${timeoutMs}ms`));
}, timeoutMs);
});
}