JavaScript API

Drive the chat from your code.

Authenticate users automatically, switch rooms, react to incoming messages — all from your existing JavaScript.

Install

Paste this once in your site's <head>. Everything else is optional.

install.html
<script>
  window.embeddedChatAsyncInit = function() {
    embeddedChat.init("YOUR_PROJECT_ID");
  };
  (function() {
    var s = document.createElement("script");
    s.async = true;
    s.src = "//www.embedded-chat.com/resource/chat.js";
    document.head.appendChild(s);
  })();
</script>
METHOD

embeddedChat.setUser()

Log a user in automatically using your own auth system.

Signature

embeddedChat.setUser({"name": "John", "profile_image": "https://..."})

Parameters

  • name — display name shown in chat. required
  • profile_image — avatar URL. optional
  • email — visitor's email; used for engagement analytics. optional

Call this before embeddedChat.init().

example.html
<script>
  window.embeddedChatAsyncInit = function() {
    embeddedChat.setUser({
      name: "Jane Doe",
      profile_image: "https://example.com/avatars/jane.png"
    });
    embeddedChat.init("YOUR_PROJECT_ID");
  };
</script>
METHOD

embeddedChat.setRoom()

Send users into a specific chat room. Useful for per-product or per-page channels.

Signature

embeddedChat.setRoom("room-id", "Optional pretty name")

Parameters

  • room_id — unique identifier. Determines which message stream the user joins.
  • name — display label for the room. optional

After init, use embeddedChat.changeRoom("other-id", "Optional pretty name") to switch.

per-product-room.html
<script>
  window.embeddedChatAsyncInit = function() {
    embeddedChat.setRoom("product-42", "Sneakers Talk");
    embeddedChat.init("YOUR_PROJECT_ID");
  };
</script>
METHOD

embeddedChat.openChat()

Open the floating chat from your own button or link. embeddedChat.closeChat() closes it again.

Signature

embeddedChat.openChat()
embeddedChat.closeChat()

Safe to call right after init(): if the chat is still loading it opens as soon as it is ready.

chat-button.html
<button onclick="embeddedChat.openChat()">Talk to us</button>
EVENT

on message received

Subscribe to incoming messages — useful for unread badges, analytics, or sound effects.

callback.html
<script>
  window.embeddedChatAsyncInit = function() {
    embeddedChat.on("message", function(msg) {
      // msg = { user: "Alice", text: "Hello!", date: 1709... }
      console.log("new message", msg);
    });
    embeddedChat.init("YOUR_PROJECT_ID");
  };
</script>
IDENTITY

Pre-authenticated visitors

If your site already knows who the visitor is, hand that identity to the chat and the welcome modal never appears. Available on every plan.

The contract

Call setUser before init(). These three keys are the entire payload — any other field is ignored.

  • name — display name shown in chat. required
  • email — the identity key: same email means the same person across visits, devices and pages. strongly recommended
  • profile_image — avatar URL. optional

Send email if you want identity to stick. Without it, every page load mints a brand-new visitor — a returning reader loses their history and their own messages stop being recognised as theirs.

What you get

  • Visitors never type a name or pick a sign-in method.
  • @mentions resolve to a stable person rather than a free-typed nickname.
  • Names and avatars match the ones on your own site.

What this is not

The identity is asserted by the page, not proved to us. setUser runs in the browser, so anyone reading your page source can call it with any name they like. It is the right tool for skipping a sign-in your visitor already completed on your site. It is not an authorization boundary — don't use it to gate anything that costs you money if someone impersonates a member.

Pair it with Behavior → Require authentication to drop the anonymous nickname box. Note that the Google and Facebook buttons stay unless you also turn on Hide social sign-in.

Need identity we actually verify? That's signed visitor tokens — same call, proof instead of an assertion.

When your server renders the page

Print the signed-in user into the template and pass it straight through.

layout.html
<script>
  // currentUser is whatever your server printed into the page.
  window.embeddedChatAsyncInit = function() {
    if (currentUser) {
      embeddedChat.setUser({
        name: currentUser.name,
        email: currentUser.email,
        profile_image: currentUser.avatarUrl
      });
    }
    embeddedChat.init("YOUR_PROJECT_ID");
  };
</script>

When you can't run server code

Hosted platforms that expose the logged-in member to the browser need one block in the site header — no theme files, no backend. Ghost, for example:

code-injection.html
<script>
  window.embeddedChatAsyncInit = function() {
    fetch("/members/api/member/")
      .then(function(r) { return r.json(); })
      .catch(function() { return null; })
      .then(function(member) {
        if (member && member.email) {
          embeddedChat.setUser({
            name: member.name || member.email.split("@")[0],
            email: member.email,
            profile_image: member.avatar_image || ""
          });
        }
        embeddedChat.init("YOUR_PROJECT_ID");
      });
  };
</script>
SERVER · PRO

Signed visitor tokens

The same pre-authentication, except we verify it. Your server signs a short-lived HS256 token with your project's own key; we check the signature before we believe a single claim in it. A visitor who edits your page source gets nowhere.

Get your signing key

Admin → Install → Pre-authenticated visitors → Generate key. It is per project, and it never leaves your server — anyone holding it can sign in as any of your users. Rotate it from the same place; every token already issued stops working the moment you do.

Payload

{
  "user_id": "your-internal-user-42",   // required — your id, your namespace
  "username": "Jane Doe",
  "email": "jane@example.com",
  "image_url": "https://example.com/avatars/jane.png",
  "exp": 1730000000                     // required — unix seconds, max 24h out
}

What we enforce

  • HS256 only. No alg negotiation, so none is not an option.
  • exp is mandatory and capped 24h ahead. A token that never expires is a password, and we won't accept one.
  • user_id is scoped to your project. Another customer signing the same id is a different person, always.
  • is_admin is ignored. Moderator standing comes from your admin account and nothing you sign can grant it.
  • A token that fails any of the above is treated as anonymous — never as an unsigned setUser.
server.py
# Sign once per page load, for the user your session already identified.
import jwt, time

token = jwt.encode({
    "user_id": str(request.user.id),
    "username": request.user.full_name,
    "email": request.user.email,
    "image_url": request.user.avatar_url,
    "exp": int(time.time()) + 3600,
}, EMBEDDED_CHAT_SIGNING_KEY, algorithm="HS256")

Then hand it to the same setUser you'd use otherwise — one key, nothing else:

page.html
<script>
  window.embeddedChatAsyncInit = function() {
    embeddedChat.setUser({ token: "<the token your server just signed>" });
    embeddedChat.init("YOUR_PROJECT_ID");
  };
</script>

Sending token replaces the unsigned fields — you can't mix the two, by design.

SERVER

Webhooks

Subscribe to chat events from outside the browser — Slack notifications, CRM lead capture, Zapier flows, custom analytics, anything. We POST a signed JSON payload; you verify the signature and process.

Configure

Panel → Integrations → Webhooks (Pro plan). Add a URL, pick events, optionally provide your own secret (or we'll generate one once and show it to you a single time). Pause, edit or delete a webhook there too, and see how its last delivery went.

Events

  • message_posted — every new chat message.
  • mention_received — fires alongside message_posted when the message contains @usernames.
  • message_pinned — admin pins a message to the top of a room.

Headers

  • Content-Type: application/json
  • X-EC-Event — the event name (matches the body's event field).
  • X-EC-Signature — sha256=<hex>. HMAC-SHA256 over the raw body bytes, keyed with your shared secret.

Behavior

  • Best-effort delivery from a background queue, one attempt per event: a failed delivery isn't retried, so design your receiver to be idempotent and tolerant of gaps.
  • 3-second timeout per delivery. Slow receivers get marked last_status=null and the chat moves on.
  • No redirect-following. A 3xx response is treated as an error so a hostile receiver can't 302 us into SSRF.
  • Public hosts only. URLs pointing at private/loopback IP literals are rejected at save time.

Payload

message_posted.json
{
  "event": "message_posted",
  "data": {
    "comment_id": 12345,
    "room_id": 42,
    "channel": "general",
    "username": "alice",
    "message": "hi @bob, see this →",
    "is_admin": false,
    "mentions": ["bob"]
  }
}

Verifying the signature

verify.py
import hmac, hashlib

def verify(raw_body, header_sig, secret):
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header_sig)

Common patterns

💬
Slack relay

Subscribe to message_posted; format and forward to a Slack incoming-webhook.

🎯
CRM lead capture

First-message-from-user → POST to your CRM as a fresh lead. The username + room id make a clean foreign key.

📨
@mention alerts

On mention_received, look up the email of the mentioned user and send them a "you were mentioned" email.

AGENTS

AI agents (MCP)

Let your AI agent set up and run the chat for you. Embedded Chat is an MCP server: Claude, ChatGPT, Claude Code, Cursor, Codex or any MCP client can install the snippet, match the chat to your site, set up moderation, rooms and administrators, answer your visitors, moderate the rooms, run polls, read your analytics and billing, and write to our team when something can't be done from there. The dashboard is still there whenever you want it. Step-by-step setup for each app: Connect AI agents.

Connect

  • Server: https://www.embedded-chat.com/mcp/ (Streamable HTTP).
  • Sign in with OAuth. The first time, your client opens an Embedded Chat page where you sign in and click Allow. Nothing to copy.
  • Or use a token if your client only takes a header: Panel → Integrations → AI agents (MCP) → Create token, sent as Authorization: Bearer <token>. It is shown once.
  • Every connected app and token is listed in Integrations, where you can revoke it. Changing or resetting your password revokes them all.

Claude and ChatGPT

In claude.ai, go to Settings → Connectors → Add custom connector and paste the server URL. In ChatGPT, add it as a custom connector (developer mode). Then sign in to Embedded Chat when asked.

Claude Code

terminal
claude mcp add --transport http embedded-chat https://www.embedded-chat.com/mcp/
# then run /mcp in Claude Code to sign in

Cursor, Windsurf and other clients

mcp.json
{
  "mcpServers": {
    "embedded-chat": {
      "url": "https://www.embedded-chat.com/mcp/",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

Tools

Reads run without asking. Changes are marked destructive, so your AI client asks you first; those marked preview also answer with a preview and act only when the agent repeats the call with confirm.

ToolWhat it doesKind
get_accountAccount overviewread
get_installInstall snippetread
get_settingsRead settingsread
update_settingsChange settingschange
list_themesList color themesread
set_themeSet the chat colorschange
delete_themeDelete a custom themechange, preview
list_roomsList roomsread
create_roomCreate a roomchange
rename_roomRename a roomchange
archive_roomArchive or restore a roomchange
clear_room_messagesDelete a room's messageschange, preview
list_teammatesList chat administratorsread
add_teammateAdd a chat administratorchange
update_teammateChange a chat administratorchange
remove_teammateRemove a chat administratorchange, preview
list_webhooksList webhooksread
save_webhookAdd or change a webhookchange
delete_webhookDelete a webhookchange, preview
get_visitor_token_secretRead the visitor-token keyread
rotate_visitor_token_secretReplace the visitor-token keychange, preview
upload_imageUpload an imagechange
list_emotesList custom emotesread
add_emoteAdd a custom emotechange
delete_emoteDelete a custom emotechange, preview
get_analyticsChat analyticsread
update_profileOwner profilechange
contact_supportContact Embedded Chat supportchange
list_messagesRead a room's messagesread
search_messagesSearch a room's historyread
get_room_activityRoom activity by hourread
list_online_visitorsWho is in a room nowread
send_messagePost in a room as the ownerchange, preview
edit_messageEdit the owner's messagechange, preview
delete_messageDelete a messagechange, preview
pin_messagePin or unpin a messagechange, preview
react_to_messageReact to a messagechange, preview
ban_visitorBan or unban whoever wrote a messagechange, preview
timeout_visitorMute a visitor for a whilechange, preview
list_pollsList polls and resultsread
create_pollStart a poll in a roomchange, preview
close_pollClose a pollchange, preview
list_chat_notificationsMentions and replies to the ownerread
mark_chat_notifications_readMark notifications readchange
get_billingPlan and billingread
change_planGet the link to change the planchange
cancel_planCancel the subscriptionchange, preview
resume_planKeep the planchange, preview
get_tipsTips receivedread
refresh_tips_statusCheck the tips Stripe accountchange
diagnoseWhat needs attentionread
api_readRead from the dashboard APIread
api_createCreate through the dashboard APIchange, preview
api_updateUpdate through the dashboard APIchange, preview
api_deleteDelete through the dashboard APIchange, preview

Prompts: daily_check, catch_up_on_chats, set_up_chat.

Behavior

  • Same rules as the dashboard. Every change goes through the panel's own checks, so a value the panel would refuse, or a feature your plan doesn't include, comes back as an error that says why.
  • Owner only. A connection acts as the account owner on its chat; teammates can't connect apps or create tokens.
  • It asks before acting. Anything your visitors would see and anything destructive answers with a preview first, and only happens with an explicit confirm. Cancelling also needs the monthly price repeated back.
  • No agent moves money. No tool charges a card, refunds or pays out: a plan change comes back as a link the owner opens and approves, and card details and the tips payout account stay in the panel.
  • 120 calls a minute per account, and up to 60 image uploads an hour.

Dashboard API (for AI agents)

The endpoints the dashboard itself calls, open to an AI agent through four MCP tools, one per kind of action: api_read reads, api_create creates, api_update changes and api_delete deletes. Each takes the endpoint's path and its fields in params, and runs it as the account owner, with the dashboard's own checks. The three that change something answer with a preview until the call is repeated with confirm: true. Rooms, messages, polls and projects named in a request must be your own; secrets in answers are hidden. Signing in, passwords, account deletion, billing and staff tools are not part of it, and staff accounts can't use it.

EndpointToolFieldsWhat it does
GET /blocks/api_readproject_idThe old block list.
POST /chat/commands.json/api_readchannelThe slash commands the owner can type in a room.
GET /chat/gifsearch/api_readq, limitSearch GIFs to post.
POST /chat/messagecontext/api_readchannel, message_id, windowThe messages around one message.
GET /chat/notifications/api_read—The owner's mentions and replies (the chat's bell).
POST /chat/users/api_readchannelWho is in a room now.
GET /chat/welcomebotconfig/api_readchannelThe welcome bot's greeting for a room.
GET /emote/list/api_readproject_idThe custom emotes.
GET /emote/picker/api_readchannel (room id)The emotes the chat's picker shows in a room.
GET /panel/analytics/api_readproject_idThe analytics page's numbers.
GET /profile/subadminlist/api_read—The chat administrators.
POST /project/bannedips/api_readproject_idThe banned IP list, as text.
POST /project/checkplan/api_readevent_senderWhether the plan includes a settings section's features.
GET /project/invoices/api_read—The latest invoices.
GET /project/theme/api_readtheme_idOne color theme's colors.
GET /project/webhooks/api_readproject_idThe webhooks and their last delivery.
GET /project/wordpressintegration/api_readproject_idThe WordPress plugin (.zip) with the snippet.
GET /replay/events/api_readproject_id, room_id, from, to, q, limitMessages in a time range (Replay).
GET /replay/histogram/api_readproject_id, room_id, daysMessages per hour (Replay's heatmap).
POST /room/adminchat/api_readroom_idA room as the owner's Live chat shows it, as HTML.
GET /room/export/api_readid (room)A room's messages as CSV.
GET /room/history/api_readroom_idA room's whole history, as HTML.
GET /room/historypage/api_readroom_id, pageOne page of a room's history, as HTML.
GET /room/list/api_read—The chat's rooms, with last activity and share codes.
GET /rooms/api_readproject_id, statusThe rooms table of the panel.
GET /tip/list/api_readproject_idTips received and the Stripe account's state.
POST /chat/banuser/api_createchannel, message_idBan the IP that posted a message.
POST /chat/message/api_createmessage (JSON: message, channel, socket_id, parent_id)Post a message in a room as the owner.
POST /chat/pollcreate/api_createchannel, question, optionsStart a poll in a room.
POST /chat/pollvote/api_createchannel, poll_id, option_idVote in a poll as the owner.
POST /chat/typing/api_createchannel, usernameShow the owner typing in a room.
POST /project/webhooksave/api_create / api_updateproject_id, url, events, active, secret, id (to change one)Add a webhook, or change one when id is given.
POST /room/block/api_createroom_identifier, user_id, token, ipBlock a visitor in a room (the old block list).
POST /room/create/api_createnameCreate a room by hand.
POST /room/exportemail/api_createid (room)Email a room's CSV to the owner.
POST /chat/editmessage/api_updatechannel, message_id, messageEdit the owner's own message.
POST /chat/notifications/markread/api_updateid (or none for all)Mark the owner's notifications read.
POST /chat/pinmessage/api_updatechannel, message_idPin a message.
POST /chat/pollclose/api_updatechannel, poll_idClose a poll.
POST /chat/react/api_updatechannel, message_id, emojiAdd or take back the owner's reaction.
POST /chat/unpinmessage/api_updatechannel, message_idUnpin a message.
POST /profile/timezone/api_updatetzThe owner's time zone.
POST /room/rename/api_updateroom_id, nameRename a room.
POST /room/unarchive/api_updateroom_idRestore an archived room.
POST /block/delete/api_deleteblock_idLift a block from the old block list.
POST /chat/deletemessage/api_deletechannel, message_idDelete a message for everyone.
POST /chat/deleteownmessage/api_deletechannel, message_idDelete one of the owner's own messages.
POST /chat/unbanuser/api_deletechannel, message_idLift the ban on the IP that posted a message.
POST /emote/delete/api_deleteproject_id, emote_idDelete a custom emote.
POST /project/deletetheme/api_deletetheme_idDelete one of the account's own themes.
POST /project/webhookdelete/api_deleteproject_id, idDelete a webhook.
POST /room/clean/api_deleteroom_idDelete every message in a room.
POST /room/delete/api_deleteroom_idArchive a room (its history is kept).