Installing the widget

One script tag, loaded asynchronously. Everything else is configuration you set in the dashboard, not attributes you maintain in markup.

The snippet

Paste this before </body>. It is a loader, not the widget — a ~1 KB stub that queues any early Zealoop(…) calls, then fetches the real bundle asynchronously. It can never block or break your page, and it never changes: widget updates ship on our side.

html
<!-- Zealoop messenger -->
<script>
  window.zealoop = { publicKey: "pk_live_your_key", apiUrl: "https://api.zealoop.com" };
  (function () {
    var i = function () { i.q.push(arguments); };
    i.q = [];
    window.Zealoop = i;
    var s = document.createElement("script");
    s.async = true;
    s.src = "https://api.zealoop.com/widget.js";
    document.head.appendChild(s);
  })();
</script>

Your public key is on the dashboard home, and it is not a secret — it identifies which workspace the widget belongs to. The widget secret is the one that must never appear in a page.

React and Next.js

Use the hosted loader inside an effect. The npm SDK is not published yet. For Next.js App Router, keep this in a client component and render it once in the root layout. Adapt the window declarations to your TypeScript conventions if needed.

jsx
"use client"; // needed for Next.js App Router
import { useEffect } from "react";

export default function ZealoopMessenger() {
  useEffect(() => {
    window.zealoop = { publicKey: "pk_live_your_key", apiUrl: "https://api.zealoop.com" };
    if (!window.Zealoop) {
      const z = function () { z.q.push(arguments); };
      z.q = []; window.Zealoop = z;
      const s = document.createElement("script");
      s.async = true; s.src = "https://api.zealoop.com/widget.js";
      document.head.appendChild(s);
    }
    window.Zealoop("boot");
    return () => window.Zealoop("shutdown");
  }, []);
  return null;
}

For an authenticated visitor, compute an email signature on your backend and call Zealoop("identify", …) when the user changes. Clear identity on logout. See identity signing.

To have a coding agent perform these steps, connect Zealoop through MCP.

Commands

Zealoop(…) works immediately, even before the bundle loads — calls queue and replay in order.

javascript
Zealoop("boot");     // start the widget (call once, after setting window.zealoop)
Zealoop("open");     // open the messenger
Zealoop("close");    // close it
Zealoop("toggle");
Zealoop("shutdown"); // remove the widget entirely (e.g. on logout)

Identifying the visitor

Tell the widget who is signed in. Without this the agent treats every visitor as anonymous and refuses anything gated on identity.

javascript
Zealoop("identify", {
  email: "customer@example.com",
  name: "Ava Chen",
  signature: "<hmac-sha256 of the email, computed on your server>"
});

Configuration

Appearance and behaviour live in the dashboard under Configuration, so changing them never means a deploy.

SettingWhat it does
Agent nameThe name shown in the widget header. Defaults to Zea.
GreetingThe opening message, before the visitor types anything.
LanguagePreferred reply language. The gate stage also detects the visitor's language per message.
PositionWhich corner the launcher sits in.
Allowed originsDomains permitted to load this workspace's widget. Empty means any origin.

Allowed origins

Add your production domains once you are live. With the list empty, any site that knows your workspace id can mount the widget and consume your conversation quota. It is a short list and it is worth setting.

Troubleshooting

SymptomUsual cause
No launcher appearsThe publicKey in the snippet does not match a workspace, or the current origin is not in Allowed origins.
Answers have no citationsSources are still crawling or embedding. Only READY chunks are retrievable.
Agent says it cannot look that upThe visitor is not identified, or the action requires identity. Check the trace in Inbox.
identify() silently ignoredThe signature did not verify. A wrong secret and a missing secret look identical from the browser — check your server.