Add IntelliDesk to your website or app

Put your AI agent on any site with one line, control it from your code, and sign in your users. Building on your own backend? The REST API, webhooks and MCP server cover that. No secrets live on these pages.

Install the chat widget

Paste this before </body> on every page where the chat should appear. Your snippet, with your address and agent filled in, is in the dashboard under Deploy → Install.

HTML
<script src="https://YOUR_APP/widget.js" data-agent="AGENT_SLUG_OR_ID" async></script>
  • data-agent is the agent's slug or id. Renaming the slug later doesn't break pages that use the old one.
  • To keep other websites from showing your agent, list your domains in Deploy → Chat widget → Embed → Allowed domains. Subdomains of a listed domain are allowed too.
  • Two scripts with different data-agent values on one page each add their own chat.
  • data-origin points the script at IntelliDesk when you serve widget.js from your own server.

To call the widget from your own code (below), add the queue first: calls made before widget.js loads wait and run once it has.

HTML
<script>
  // Lets your code call intellidesk(...) before widget.js has loaded: calls wait in a queue.
  window.intellidesk = window.intellidesk || function () {
    (window.intellidesk.q = window.intellidesk.q || []).push(arguments);
  };
  window.intellideskConfig = {
    showFloatingInitialMessages: true,
    floatingInitialMessagesDelay: 2,
    floatingMessagesOncePerSession: true
  };
</script>
<script src="https://YOUR_APP/widget.js" data-agent="AGENT_SLUG_OR_ID" async></script>

Embed it in the page (iframe)

Shows the chat inside your layout instead of as a floating bubble: a help page, a support tab in your app. Allowed domains apply to the page the iframe is on.

HTML
<iframe
  src="https://YOUR_APP/embed/AGENT_SLUG_OR_ID"
  title="Chat"
  width="420" height="720"
  allow="microphone; clipboard-write"
  style="border:0;border-radius:16px"
></iframe>

The iframe is always visible and has no JavaScript API. To open, close or identify from code, use the widget.

With allowed domains set, the iframe checks which site it's on. Firefox only tells it through the referrer, so a page sent with Referrer-Policy: no-referrer can't show a domain-locked agent. Keep the default policy (strict-origin-when-cross-origin) or use the widget.

React, Next.js and other single-page apps

Load the script once, in your root layout. The widget follows page changes on its own, so the agent and your team see which page a visitor is on.

app/layout.tsx
// app/layout.tsx (Next.js App Router): the queue, then the widget, once for every page.
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script id="intellidesk-queue" strategy="beforeInteractive">
          {`window.intellidesk = window.intellidesk || function () {
            (window.intellidesk.q = window.intellidesk.q || []).push(arguments);
          };`}
        </Script>
        <Script src="https://YOUR_APP/widget.js" data-agent="AGENT_SLUG_OR_ID" strategy="afterInteractive" />
      </body>
    </html>
  );
}

When your app knows who is signed in, identify them. Call resetUser on logout, so the next person on that browser starts fresh instead of seeing their chat:

ChatIdentity.tsx
"use client";
// Signs the user in to the chat once your app knows who they are.
import { useEffect } from "react";

declare global {
  interface Window { intellidesk?: (...args: unknown[]) => void }
}

export function ChatIdentity({ user }: { user: { id: string; name: string; email: string } | null }) {
  useEffect(() => {
    if (!user) return;
    // Your server signs the user id (see /docs/identity) and returns { user_id, user_hash }.
    fetch("/api/intellidesk-identity")
      .then((r) => r.json())
      .then((signed) => window.intellidesk?.("identify", { ...signed, name: user.name, email: user.email }));
  }, [user?.id]);
  return null;
}

// In your logout handler:
window.intellidesk?.("resetUser");

JavaScript API

The global is window.intellidesk. Every method also works in the queue form, intellidesk("open").

JavaScript
window.intellidesk.open();                        // open the chat
window.intellidesk.open({ message: "Hi" });        // open it and send a message
window.intellidesk.close();
window.intellidesk.resetChat();                    // new conversation, same visitor
window.intellidesk.setInitialMessages(["Welcome back."]);
window.intellidesk.identify({ user_id: "u_123", user_hash: "…", name: "Ada" });
window.intellidesk.resetUser();                    // on logout: a new anonymous visitor
window.intellidesk.addEventListener("ready", (e) => console.log("chat loaded", e.agentId));
window.intellidesk.addEventListener("open", () => analytics.track("chat_opened"));
window.intellidesk.addEventListener("user-message", (e) => console.log(e.message));
window.intellidesk.addEventListener("assistant-message", (e) => console.log(e.message, e.conversationId));
window.intellidesk.addEventListener("error", (e) => console.warn("chat unavailable:", e.reason));

// The same calls in the queue form, which also works before widget.js has loaded:
window.intellidesk("open");
window.intellidesk("identify", window.intellideskUserConfig);

Methods

MethodWhat it does
open(options?)Opens the chat. { message: "…" } also sends that message.
close()Closes the chat.
resetChat()Starts a new conversation. The visitor, and their earlier chats, stay.
setInitialMessages(list)Replaces the greeting shown before the visitor writes.
identify(user)Signs in a user: user_id + user_hash, or token (a JWT). Other fields (name, email, plan…) are shown to the agent. Someone else signing in on the same browser gets a new chat. See Identity verification.
resetUser()Call on logout. Forgets the user and starts a new anonymous visitor: no chat, no history, no saved pages.
addEventListener(type, fn)Listens for an event (below).
removeEventListener(type, fn)Stops listening.

Events

EventFires whenDetails
readyThe chat has loaded. A listener added later still gets it.agentId
openThe chat opens.agentId
closeThe chat closes.agentId
user-messageThe visitor sends a message.message, agentId
assistant-messageThe agent finishes a reply.message, agentId, conversationId
errorThe chat can't load, or can't chat with this visitor.reason: domain_not_allowed, agent_not_found, identity_required or load_failed

Options

Set window.intellideskConfig before the script. Keys you set override the ones saved on Deploy → Chat widget → Content.

OptionWhat it does
hideLauncherNo floating bubble: open the chat from your own button. Same as data-launcher="hidden" on the script tag.
showFloatingInitialMessagesShows the greeting next to the bubble before the chat is opened.
floatingInitialMessagesDelaySeconds before that greeting appears (0 to 10, default 2).
floatingMessagesOncePerSessionShows it once per browser session (default true).

Open the chat from your own button

HTML
<script>
  window.intellideskConfig = { hideLauncher: true };   // or data-launcher="hidden" on the script tag
</script>
<script src="https://YOUR_APP/widget.js" data-agent="AGENT_SLUG_OR_ID" async></script>

<button onclick="window.intellidesk('open')">Chat with us</button>

Identify signed-in users

Tell the agent who a signed-in user is. It can greet them by name, the chat shows up on their contact in IntelliDesk, and actions like order lookups know whose order it is. Your server signs the user id with the agent's identity secret (Deploy → Chat widget → Embed); the browser passes it on.

JavaScript
window.intellideskUserConfig = {
  user_id: "u_123",
  user_hash: "HMAC_SHA256_HEX_FROM_YOUR_SERVER",
  name: "Ada",
  current_page: location.pathname,
};
window.intellidesk("identify", window.intellideskUserConfig);

Server code for Node, Python, PHP, Ruby and Go, JWTs, and what changes when verification is required: Identity verification.

Troubleshooting

The widget explains problems in the browser console, prefixed [IntelliDesk]. Visitors only see a short message.

Console saysFix
isn't one of the agent's allowed domainsAdd the site under Deploy → Chat widget → Embed → Allowed domains.
There is no agent "…"Check data-agent: Deploy → Install shows the right value.
Identity verification requiredThis agent only chats with signed-in users: identify them (above), or turn verification off.
The chat couldn't loadThe script can't reach IntelliDesk: check data-origin and your Content-Security-Policy.

With a Content-Security-Policy, allow your IntelliDesk address in script-src, connect-src (with wss: for live replies), img-src and, for the iframe, frame-src. The widget's animations and hover styles also need style-src 'unsafe-inline'; the chat works without them.

Floating initial messages

Save the flags on Deploy → Chat widget → Content. The bubble uses that saved widget content when the host page has no window.intellideskConfig. A host config set before the script loads still overrides matching keys. Default delay is 2 seconds. floatingMessagesOncePerSession defaults to true and uses id_float_{agentId} in sessionStorage.

What the widget stores in the browser

KeyWhat it is
id_visitor_{agentId}The anonymous visitor id (localStorage).
id_convo_{source}_{agentId}The visitor's current conversation, for the widget or the help page.
id_user_{agentId}The signed-in user that conversation belongs to.
id_journey_{agentId}The last pages viewed (up to 10, within a day), sent with the first message.
id_float_{agentId}Floating messages already shown this session (sessionStorage).

resetChat() clears the conversation and keeps the visitor. resetUser() clears all of it. When the browser blocks storage, the widget still works for that page view.

Public routes

What the widget and help page call. They need no key; the agent's domain lock and rate limits apply.

MethodRouteNotes
GET/api/public/agent/:agentIdOrSlugWidget config. Allowed domain + public visibility. Id or slug.
GET/api/public/conversation/:idRestore messages when visitorId matches.
GET/api/public/conversationsThis visitor’s widget/help threads.
DELETE/api/public/conversation/:idVisitor may delete their own thread.
POST/api/public/conversation/:id/transcriptEmail the thread to the address the visitor gave in it (3 an hour).
POST/api/public/chatHTTP chat. Same domain, rate-limit, and identify rules as Socket.io. Optional attachmentIds after POST /api/public/attachment. Optional voice=true marks the visitor turn as spoken.
POST/api/chat/:agentIdHelp-proxy alias for POST /api/public/chat. agentId can live in the path.
GET/__id/*Help page proxy assets and /__id/api/* public routes. /__id/help/:slug is the rewritten help HTML.
POST/api/public/leadsLead form (also /api/leads). Domain lock; returns only the visitor's own details.
POST/api/public/feedbackThumbs up or down on a reply (also /api/feedback). Domain lock.
POST/api/public/attachmentVisitor file upload (images, pdf, txt, docx). Domain + rate-limit + channel-off rules.
GET/api/public/attachment/:idDownload a visitor file when visitorId matches.
GET/api/public/demoDemo agent payload.
Socket.iochat:startPublic stream. source=widget or help. Optional handoff=true requests a human when Live chat is on.
Socket.iochat:join / operator:messageVisitor joins a conversation room and receives operator replies.
Playground chats stay in Activity as source=playground. The widget is source=widget. Help page: /help/AGENT_ID. Customer origin rewrite: /help + /__id/* (see Deploy → Help → Domain). Local path: /help-proxy/AGENT_ID. For your own backend, use the REST API with a secret key instead.