Embed codes
Every storefront we’ve ever seen falls into one of seven patterns. Pick the one that matches yours and paste the snippet. Both the store ID and the widget key are visible under Dashboard → Widget → Embed.
A note about origins
Widget → Allowed origins first.Plain HTML / vanilla site
The fallback for any static or server-rendered site. Paste before </body>.
<script
src="https://cdn.zubbyai.com/widget/v1/loader.js"
data-store-id="YOUR_STORE_ID"
data-widget-key="YOUR_WIDGET_KEY"
async
></script>Shopify (Online Store 2.0)
Use the theme App Embed — covered in Embed the widget on Shopify. If you need to inline the snippet (Vintage themes or for an A/B test), drop the above HTML snippet into theme.liquid before </body>. Use {{ shop.permanent_domain }} for the store ID.
WooCommerce
The plugin handles injection automatically. If you want to force-inject on a custom theme that doesn’t call wp_footer, add the snippet to your theme’s footer.php manually.
Webflow
Site Settings → Custom code → Footer Code. Paste the snippet and publish. The launcher appears on every page.
Squarespace / Wix
Squarespace: Settings → Advanced → Code Injection → Footer. Wix: Settings → Custom Code → Add custom code → Body end. Paste the snippet. Note that Wix Free plans don’t allow custom code.
Next.js (App Router)
Drop a script tag in your root layout. strategy="afterInteractive" is the right strategy — we don’t need to run before hydration.
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://cdn.zubbyai.com/widget/v1/loader.js"
data-store-id={process.env.NEXT_PUBLIC_ZUBBY_STORE_ID}
data-widget-key={process.env.NEXT_PUBLIC_ZUBBY_WIDGET_KEY}
strategy="afterInteractive"
/>
</body>
</html>
);
}React (Vite / CRA)
Inject in a useEffect in your root component so the script runs after first render:
useEffect(() => {
const s = document.createElement("script");
s.src = "https://cdn.zubbyai.com/widget/v1/loader.js";
s.async = true;
s.dataset.storeId = import.meta.env.VITE_SP_STORE_ID;
s.dataset.widgetKey = import.meta.env.VITE_SP_WIDGET_KEY;
document.body.appendChild(s);
return () => {
s.remove();
delete window.Zubby;
};
}, []);Hydrogen / Remix
Add the <script> tag inside your root App component’s document — make sure it’s rendered server-side so it isn’t blocked by the client-side router.
Mobile app (WebView)
For Shopify mobile apps or other native shells that wrap a WebView, the widget runs as-is once injected into the HTML you render. If you’re using a fully-native checkout, the widget can’t inject — use the Zubby REST API directly from your Swift / Kotlin code.
Programmatic API
Once the widget is loaded, a small API hangs off window.Zubby:
// Open the chat panel programmatically
window.Zubby.open();
// Send a prefilled message (e.g. from a CTA button click)
window.Zubby.send("Tell me about the spring collection");
// Update shopper context after login
window.Zubby.update({ customer: { email: "alex@example.com" } });
// Listen for events
window.Zubby.on("conversation:started", (e) => console.log(e));
window.Zubby.on("handoff:requested", (e) => console.log(e));
window.Zubby.on("conversion:attributed", (e) => console.log(e));Verify the embed
After publishing, open your storefront and check:
- The launcher renders within ~2 seconds.
- In DevTools → Network,
/api/v1/widget/bootstrapreturns 200. window.Zubby.readyistrueafter the load event.
Anything off? See Widget not loading.