Subscribe to message_posted; format and forward to a Slack incoming-webhook.
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.
<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>
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. requiredprofile_image— avatar URL. optionalemail— visitor's email; used for engagement analytics. optional
Call this before embeddedChat.init().
<script> window.embeddedChatAsyncInit = function() { embeddedChat.setUser({ name: "Jane Doe", profile_image: "https://example.com/avatars/jane.png" }); embeddedChat.init("YOUR_PROJECT_ID"); }; </script>
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.
<script> window.embeddedChatAsyncInit = function() { embeddedChat.setRoom("product-42", "Sneakers Talk"); embeddedChat.init("YOUR_PROJECT_ID"); }; </script>
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.
<button onclick="embeddedChat.openChat()">Talk to us</button>
on message received
Subscribe to incoming messages — useful for unread badges, analytics, or sound effects.
<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>
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. requiredemail— the identity key: same email means the same person across visits, devices and pages. strongly recommendedprofile_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.
<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:
<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>
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
HS256only. Noalgnegotiation, sononeis not an option.expis mandatory and capped 24h ahead. A token that never expires is a password, and we won't accept one.user_idis scoped to your project. Another customer signing the same id is a different person, always.is_adminis 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.
# 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:
<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.
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 alongsidemessage_postedwhen the message contains@usernames.message_pinned— admin pins a message to the top of a room.
Headers
Content-Type: application/jsonX-EC-Event— the event name (matches the body'seventfield).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=nulland 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
{
"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
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
First-message-from-user → POST to your CRM as a fresh lead. The username + room id make a clean foreign key.
On mention_received, look up the email of the mentioned user and send them a "you were mentioned" email.
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
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
{
"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.
| Tool | What it does | Kind |
|---|---|---|
get_account | Account overview | read |
get_install | Install snippet | read |
get_settings | Read settings | read |
update_settings | Change settings | change |
list_themes | List color themes | read |
set_theme | Set the chat colors | change |
delete_theme | Delete a custom theme | change, preview |
list_rooms | List rooms | read |
create_room | Create a room | change |
rename_room | Rename a room | change |
archive_room | Archive or restore a room | change |
clear_room_messages | Delete a room's messages | change, preview |
list_teammates | List chat administrators | read |
add_teammate | Add a chat administrator | change |
update_teammate | Change a chat administrator | change |
remove_teammate | Remove a chat administrator | change, preview |
list_webhooks | List webhooks | read |
save_webhook | Add or change a webhook | change |
delete_webhook | Delete a webhook | change, preview |
get_visitor_token_secret | Read the visitor-token key | read |
rotate_visitor_token_secret | Replace the visitor-token key | change, preview |
upload_image | Upload an image | change |
list_emotes | List custom emotes | read |
add_emote | Add a custom emote | change |
delete_emote | Delete a custom emote | change, preview |
get_analytics | Chat analytics | read |
update_profile | Owner profile | change |
contact_support | Contact Embedded Chat support | change |
list_messages | Read a room's messages | read |
search_messages | Search a room's history | read |
get_room_activity | Room activity by hour | read |
list_online_visitors | Who is in a room now | read |
send_message | Post in a room as the owner | change, preview |
edit_message | Edit the owner's message | change, preview |
delete_message | Delete a message | change, preview |
pin_message | Pin or unpin a message | change, preview |
react_to_message | React to a message | change, preview |
ban_visitor | Ban or unban whoever wrote a message | change, preview |
timeout_visitor | Mute a visitor for a while | change, preview |
list_polls | List polls and results | read |
create_poll | Start a poll in a room | change, preview |
close_poll | Close a poll | change, preview |
list_chat_notifications | Mentions and replies to the owner | read |
mark_chat_notifications_read | Mark notifications read | change |
get_billing | Plan and billing | read |
change_plan | Get the link to change the plan | change |
cancel_plan | Cancel the subscription | change, preview |
resume_plan | Keep the plan | change, preview |
get_tips | Tips received | read |
refresh_tips_status | Check the tips Stripe account | change |
diagnose | What needs attention | read |
api_read | Read from the dashboard API | read |
api_create | Create through the dashboard API | change, preview |
api_update | Update through the dashboard API | change, preview |
api_delete | Delete through the dashboard API | change, 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.
| Endpoint | Tool | Fields | What it does |
|---|---|---|---|
GET /blocks/ | api_read | project_id | The old block list. |
POST /chat/commands.json/ | api_read | channel | The slash commands the owner can type in a room. |
GET /chat/gifsearch/ | api_read | q, limit | Search GIFs to post. |
POST /chat/messagecontext/ | api_read | channel, message_id, window | The messages around one message. |
GET /chat/notifications/ | api_read | — | The owner's mentions and replies (the chat's bell). |
POST /chat/users/ | api_read | channel | Who is in a room now. |
GET /chat/welcomebotconfig/ | api_read | channel | The welcome bot's greeting for a room. |
GET /emote/list/ | api_read | project_id | The custom emotes. |
GET /emote/picker/ | api_read | channel (room id) | The emotes the chat's picker shows in a room. |
GET /panel/analytics/ | api_read | project_id | The analytics page's numbers. |
GET /profile/subadminlist/ | api_read | — | The chat administrators. |
POST /project/bannedips/ | api_read | project_id | The banned IP list, as text. |
POST /project/checkplan/ | api_read | event_sender | Whether the plan includes a settings section's features. |
GET /project/invoices/ | api_read | — | The latest invoices. |
GET /project/theme/ | api_read | theme_id | One color theme's colors. |
GET /project/webhooks/ | api_read | project_id | The webhooks and their last delivery. |
GET /project/wordpressintegration/ | api_read | project_id | The WordPress plugin (.zip) with the snippet. |
GET /replay/events/ | api_read | project_id, room_id, from, to, q, limit | Messages in a time range (Replay). |
GET /replay/histogram/ | api_read | project_id, room_id, days | Messages per hour (Replay's heatmap). |
POST /room/adminchat/ | api_read | room_id | A room as the owner's Live chat shows it, as HTML. |
GET /room/export/ | api_read | id (room) | A room's messages as CSV. |
GET /room/history/ | api_read | room_id | A room's whole history, as HTML. |
GET /room/historypage/ | api_read | room_id, page | One 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_read | project_id, status | The rooms table of the panel. |
GET /tip/list/ | api_read | project_id | Tips received and the Stripe account's state. |
POST /chat/banuser/ | api_create | channel, message_id | Ban the IP that posted a message. |
POST /chat/message/ | api_create | message (JSON: message, channel, socket_id, parent_id) | Post a message in a room as the owner. |
POST /chat/pollcreate/ | api_create | channel, question, options | Start a poll in a room. |
POST /chat/pollvote/ | api_create | channel, poll_id, option_id | Vote in a poll as the owner. |
POST /chat/typing/ | api_create | channel, username | Show the owner typing in a room. |
POST /project/webhooksave/ | api_create / api_update | project_id, url, events, active, secret, id (to change one) | Add a webhook, or change one when id is given. |
POST /room/block/ | api_create | room_identifier, user_id, token, ip | Block a visitor in a room (the old block list). |
POST /room/create/ | api_create | name | Create a room by hand. |
POST /room/exportemail/ | api_create | id (room) | Email a room's CSV to the owner. |
POST /chat/editmessage/ | api_update | channel, message_id, message | Edit the owner's own message. |
POST /chat/notifications/markread/ | api_update | id (or none for all) | Mark the owner's notifications read. |
POST /chat/pinmessage/ | api_update | channel, message_id | Pin a message. |
POST /chat/pollclose/ | api_update | channel, poll_id | Close a poll. |
POST /chat/react/ | api_update | channel, message_id, emoji | Add or take back the owner's reaction. |
POST /chat/unpinmessage/ | api_update | channel, message_id | Unpin a message. |
POST /profile/timezone/ | api_update | tz | The owner's time zone. |
POST /room/rename/ | api_update | room_id, name | Rename a room. |
POST /room/unarchive/ | api_update | room_id | Restore an archived room. |
POST /block/delete/ | api_delete | block_id | Lift a block from the old block list. |
POST /chat/deletemessage/ | api_delete | channel, message_id | Delete a message for everyone. |
POST /chat/deleteownmessage/ | api_delete | channel, message_id | Delete one of the owner's own messages. |
POST /chat/unbanuser/ | api_delete | channel, message_id | Lift the ban on the IP that posted a message. |
POST /emote/delete/ | api_delete | project_id, emote_id | Delete a custom emote. |
POST /project/deletetheme/ | api_delete | theme_id | Delete one of the account's own themes. |
POST /project/webhookdelete/ | api_delete | project_id, id | Delete a webhook. |
POST /room/clean/ | api_delete | room_id | Delete every message in a room. |
POST /room/delete/ | api_delete | room_id | Archive a room (its history is kept). |