The widget is a single script tag. Copy your channel key from the channel’s Web Chat settings in Message24 (the Embed Code box), then add the script once per page.
Plain HTML
Section titled “Plain HTML”Paste the snippet before </body> on every page where the chat should appear:
<script src="https://message24.net/widget.js" data-channel-key="YOUR_CHANNEL_KEY" async></script>That is all that is required. The widget mounts once the DOM is ready, whether the script loads early or late.
To pass visitor details or page data, define window.__m24 in a script that runs before the widget script:
<script> window.__m24 = window.__m24 || {}; window.__m24.visitor = { name: "Alice", phone: "+964 750 000 0000" }; window.__m24.pageData = { productId: "sku_123", category: "Shoes", price: 79.99 };</script><script src="https://message24.net/widget.js" data-channel-key="YOUR_CHANNEL_KEY" async></script>See Configure the widget from your page for every field.
Rules for the script tag
Section titled “Rules for the script tag”- Load it from
https://message24.net/widget.js. The widget sends its API requests to the origin its script was loaded from. A copy ofwidget.jsserved from your own domain would send them to your domain instead. The file is served with no-cache headers, so you always get the current version. - Keep
data-channel-keyon the tag itself. The widget finds its configuration by reading that attribute from the running script, or from the firstscript[data-channel-key]on the page. - Loading it twice is harmless. If
#m24-widget-rootalready exists, the second copy does nothing.
Load the widget once from your app shell. Mounting it in a component that re-renders or unmounts on navigation gains nothing: the widget attaches itself to <body> and lives for the whole page.
import { useEffect } from "react";
export default function App() { useEffect(() => { window.__m24 = window.__m24 || {}; window.__m24.visitor = { name: currentUser?.name ?? "", phone: currentUser?.phone ?? "" };
if (document.getElementById("m24-widget-script")) return; const s = document.createElement("script"); s.id = "m24-widget-script"; s.src = "https://message24.net/widget.js"; s.async = true; s.dataset.channelKey = "YOUR_CHANNEL_KEY"; document.body.appendChild(s); }, []);
return <YourApp />;}Next.js
Section titled “Next.js”Use a client component that sets window.__m24 and injects the script, and render it from your root layout. The example uses the App Router; with the Pages Router, render the same component from pages/_app.tsx.
"use client";
import { useEffect } from "react";
export default function ChatWidget({ channelKey }: { channelKey: string }) { useEffect(() => { const w = window as any; w.__m24 = w.__m24 || {};
if (document.getElementById("m24-widget-script")) return; const s = document.createElement("script"); s.id = "m24-widget-script"; s.src = "https://message24.net/widget.js"; s.async = true; s.dataset.channelKey = channelKey; document.body.appendChild(s); }, [channelKey]);
return null;}import ChatWidget from "./chat-widget";
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <ChatWidget channelKey="YOUR_CHANNEL_KEY" /> </body> </html> );}A product page can then publish its data:
"use client";
import { useEffect } from "react";
export function ProductContext({ product }: { product: { id: string; name: string; price: number } }) { useEffect(() => { const w = window as any; w.__m24 = w.__m24 || {}; w.__m24.pageData = { productId: product.id, name: product.name, price: product.price, pageType: "product" }; // Clear it when the visitor navigates away. return () => { w.__m24.pageData = undefined; }; }, [product]); return null;}Clearing the data in the effect’s cleanup means it is removed before the next page renders, so a visitor who moves from one product to another never sends the old product’s data.
Single-page apps and route changes
Section titled “Single-page apps and route changes”The widget stays mounted across client-side navigation and keeps the same conversation. You do not need to reload or re-inject it.
Page context is read at the moment the visitor sends a message:
urlislocation.pathnameat send time, so it follows your router automatically.titleisdocument.titleat send time.dataiswindow.__m24.pageDataat send time.
So the only thing to do on a route change is keep window.__m24.pageData accurate: set it when a page with meaningful data renders, and clear it when the visitor leaves. Stale page data tells the AI agent the visitor is looking at a product they have already left.
Logging a user out
Section titled “Logging a user out”The conversation belongs to a visitor ID stored in the browser’s localStorage under m24_visitor_id. If several people share a browser, for example a user logs out and another logs in, remove that key and reload so the next person starts a fresh conversation:
localStorage.removeItem("m24_visitor_id");location.reload();Content Security Policy
Section titled “Content Security Policy”If your site sends a Content-Security-Policy header, allow:
script-src https://message24.netforwidget.js.connect-src https://message24.netfor the API calls and the event stream.style-src 'unsafe-inline', because the widget injects its own<style>element and inline styles.img-srcandmedia-srcfor the hosts that serve images, audio and video in replies, and for the business logo URL.
