Create your account
- Go to /signup.
- Enter your name, email, a password of at least 10 characters and a name for your workspace (usually your company or product name).
- You’re signed in to the agent portal straight away on the Free plan. If you chose a paid plan from our pricing page, you’ll be taken to Stripe Checkout to pay — you can also upgrade later from Settings → Billing.
Each workspace has a public key that starts with pk_live_. It identifies your widget and is safe to publish in your site’s HTML.
Add the snippet to your site
In the portal, open Settings → Widget and copy your snippet. It looks like this, with your own key:
<script src="https://chatmesh.ascend-digital.co.uk/widget.js" data-chatmesh-key="pk_live_..." async></script> Paste it just before the closing </body> tag on every page where you want the chat to appear. It loads asynchronously, so it won’t slow down your page. Pick your platform:
Plain HTML
<!doctype html>
<html lang="en">
<head>
<!-- … -->
</head>
<body>
<!-- … your page … -->
<script src="https://chatmesh.ascend-digital.co.uk/widget.js" data-chatmesh-key="pk_live_..." async></script>
</body>
</html> WordPress
Step-by-step guide: add live chat to WordPress.
Easiest — use a header & footer plugin. Install a code-snippets plugin such as WPCode (formerly “Insert Headers and Footers”), open its Header & Footer screen, paste the snippet into the Footer box and save. This works with every theme, including block themes, and survives theme updates.
Or — classic theme footer. Go to Appearance → Theme File Editor, open footer.php and paste the snippet immediately above <?php wp_footer(); ?> / </body>. Use a child theme so a theme update doesn’t remove it. Developers can add it from functions.php instead:
<?php
// In your child theme's functions.php
add_action('wp_footer', function () {
echo '<script src="https://chatmesh.ascend-digital.co.uk/widget.js" data-chatmesh-key="pk_live_..." async></script>';
}); Shopify
Step-by-step guide: add live chat to Shopify.
- In Shopify admin, go to Online Store → Themes.
- Next to your current theme, click … → Edit code.
- Under Layout, open
theme.liquid. - Find the closing
</body>tag (use Ctrl/Cmd + F), paste the snippet just above it and click Save.
<!-- … the rest of theme.liquid … -->
<script src="https://chatmesh.ascend-digital.co.uk/widget.js" data-chatmesh-key="pk_live_..." async></script>
</body>
</html> Laravel
Keep the key in .env and render the script in your Blade layout: add live chat to Laravel.
Next.js
Use next/script with strategy="afterInteractive" in your root layout so the widget loads once and persists across client-side navigation.
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://chatmesh.ascend-digital.co.uk/widget.js"
data-chatmesh-key="pk_live_..."
strategy="afterInteractive"
/>
</body>
</html>
);
} // pages/_app.tsx
import type { AppProps } from 'next/app';
import Script from 'next/script';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script
src="https://chatmesh.ascend-digital.co.uk/widget.js"
data-chatmesh-key="pk_live_..."
strategy="afterInteractive"
/>
</>
);
} React single-page app (Vite, Create React App)
The simplest option is to paste the snippet into your HTML template:
<!-- index.html (Vite) or public/index.html (Create React App) -->
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="https://chatmesh.ascend-digital.co.uk/widget.js" data-chatmesh-key="pk_live_..." async></script>
</body> If you’d rather load it from a component (for example, only for signed-in users), render this once near the root of your app:
// ChatMeshWidget.tsx — load the widget once from inside your app
import { useEffect } from 'react';
export function ChatMeshWidget({ publicKey }: { publicKey: string }) {
useEffect(() => {
if (document.querySelector('script[data-chatmesh-key]')) return;
const s = document.createElement('script');
s.src = 'https://chatmesh.ascend-digital.co.uk/widget.js';
s.async = true;
s.dataset.chatmeshKey = publicKey;
document.body.appendChild(s);
}, [publicKey]);
return null;
} Check it works: open your site in a private window, click the chat button and send a message. It appears instantly in the portal as a new ticket.
Customise the widget
In Settings → Widget you can change:
- Title — shown in the chat header, e.g. “Chat with us”.
- Greeting — the first message visitors see, e.g. “Hi! How can we help?”.
- Accent colour — used for the launcher, header and visitor messages. Pick a colour with enough contrast against white text (4.5:1 or more) so it stays readable.
- Position — bottom right or bottom left.
- Ask for email — ask visitors for their email before their first message, so you can follow up if they leave.
- “Powered by ChatMesh” — can be removed on the Startup and Enterprise plans.
Changes go live on your site straight away — no need to update the snippet or redeploy.
Restrict allowed origins
Your public key is visible in your page source, so anyone could copy it. To stop the widget being used on other people’s sites, add the origins that may load it under Settings → Workspace → Allowed origins:
https://example.co.uk
https://www.example.co.uk
https://staging.example.co.uk
http://localhost:3000 - An origin is the scheme, host and (optional) port — no paths or trailing slashes.
wwwand non-wwware different origins; add both if you use both.- If the list is empty, the widget works on any site. We recommend setting it before you go live.
Invite your team
- Open Settings → Team and click Invite.
- Enter your teammate’s email and choose a role:
- Agent — can view and reply to tickets, add notes and update tickets.
- Admin — everything an agent can do, plus widget settings, team, API keys and integrations.
- Send them the invite link. They choose a name and password and land in your workspace. Invites expire, and you can revoke unused ones at any time.
Each member and pending invite uses a seat (Free: 1, Startup: 5, Enterprise: unlimited). The workspace owner manages billing.
Connect Slack
Available on Startup and Enterprise. You’ll need permission to install apps in your Slack workspace.
- In the portal, go to Settings → Integrations and click Add to Slack.
- Slack asks you to approve the ChatMesh app for your workspace. Click Allow and you’ll be returned to the portal.
- Choose the channel where new chats should appear by pasting its channel ID, then save.
- If it’s a private channel, invite the app first by typing
/invite @ChatMeshin that channel.
How to find a Slack channel ID
- Open the channel in Slack (desktop or web).
- Click the channel name at the top of the conversation.
- Scroll to the bottom of the About tab — the Channel ID (it starts with
C, e.g.C0123ABCD45) is shown with a copy button.
How it works: every new chat posts a message in the channel with the ticket number and details, and the conversation continues in its thread. Reply in the thread and the visitor sees your message in the widget instantly; their replies appear in the thread. Everything is also in the portal.
Connect Discord
Available on Startup and Enterprise. You’ll need the Manage Server permission on your Discord server.
- In Settings → Integrations, click Invite bot under Discord. Choose your server and authorise the bot.
- Make sure the bot can see the channel you want to use and has permission to Send Messages, Create Public Threads, Send Messages in Threads and Read Message History.
- Enable Developer Mode in Discord: User Settings → Advanced → Developer Mode.
- Right-click (or long-press on mobile) the channel and choose Copy Channel ID.
- Paste the ID into the portal and save.
Just like Slack, each chat opens a thread; replies in the thread go straight to the visitor.
Add an AI agent
Any LLM that supports the Model Context Protocol (MCP) can work your support queue. It authenticates with an API key, and its messages are labelled as AI and attributed to the key’s name — so your team always knows who said what, and can take over at any time.
Create an API key
- Go to Settings → AI agents and click Create key.
- Give it a clear name, such as Support AI — this is shown as the author of its replies.
- Copy the secret (it starts with
cm_sk_). It’s shown only once; store it in a password manager or secret store. Revoke and replace it at any time.
API keys are secret: never put a cm_sk_ key in browser code or a public repository. The pk_live_ key in your snippet is the only key that belongs on your website.
Claude Code (remote MCP over HTTP)
claude mcp add --transport http chatmesh https://chatmesh.ascend-digital.co.uk/mcp --header "Authorization: Bearer cm_sk_..." Then ask, for example: “Check ChatMesh for open tickets and draft replies.”
Claude Desktop and other stdio MCP clients
Add this to your claude_desktop_config.json (Settings → Developer → Edit Config; on macOS it’s in ~/Library/Application Support/Claude/, on Windows in %APPDATA%\Claude\), then restart Claude Desktop. Requires Node.js 18 or later.
{
"mcpServers": {
"chatmesh": {
"command": "npx",
"args": ["-y", "@chatmesh/mcp"],
"env": {
"CHATMESH_API_KEY": "cm_sk_..."
}
}
}
} Other MCP clients that support Streamable HTTP can connect directly to https://chatmesh.ascend-digital.co.uk/mcp with the header Authorization: Bearer cm_sk_....
Available tools
| Tool | What it does |
|---|---|
list_tickets | Find open, pending or closed tickets, filtered by assignee or search text. |
get_ticket | Read a ticket with its full message history, including internal notes. |
reply_to_ticket | Send a reply the visitor sees instantly in the widget. |
add_internal_note | Leave a note for human teammates that visitors never see. |
update_ticket | Change status, priority, assignee or subject — e.g. hand off to a human. |
get_stats | Open and pending counts, closures and median first-response time. |
Suggested system prompt
Start with this and adapt it to your business. Keeping humans in the loop for anything uncertain is the single most important rule.
You are the first-line support agent for <Company>, answering website live chats through ChatMesh.
How to work:
1. Call list_tickets with status "open" to find conversations that need a reply.
2. For each ticket, call get_ticket and read the whole conversation, including internal notes from the team.
3. Reply with reply_to_ticket only when you are confident the answer is correct and covered by the
knowledge below. Be warm, concise and use British English. Never invent prices, policies or dates.
4. If you are unsure, the visitor is upset, or the request involves refunds, billing changes, legal or
security matters: do NOT reply. Instead call add_internal_note summarising the issue and what you
checked, then update_ticket to set priority "high" and leave it for a human.
5. When a question is fully answered, call update_ticket to set status "pending".
6. Never ask for passwords, card numbers or other sensitive data. Never reveal internal notes.
Knowledge:
- <Paste your product facts, FAQ, opening hours and escalation contacts here.> Prefer plain HTTP? Use the REST API
Any agent framework can use the same API key against the REST API. To react the moment a visitor writes, trigger your agent from a message.created or ticket.created webhook (next step).
# List open tickets
curl "https://chatmesh.ascend-digital.co.uk/v1/tickets?status=open&limit=20" \
-H "Authorization: Bearer cm_sk_..."
# Reply to a ticket (the visitor sees it instantly)
curl -X POST "https://chatmesh.ascend-digital.co.uk/v1/tickets/<ticket-id>/messages" \
-H "Authorization: Bearer cm_sk_..." \
-H "Content-Type: application/json" \
-d '{ "body": "Thanks for getting in touch — here is how to do that…" }' JavaScript API & webhooks
JavaScript API
Once the widget has loaded, window.ChatMesh exposes three methods:
ChatMesh.open()— open the chat window.ChatMesh.close()— close it.ChatMesh.identify({ name, email })— attach a signed-in user’s details to the conversation so they don’t have to type them.
<button type="button" id="help">Chat with us</button>
<script>
// The widget loads asynchronously, so wait for it if needed.
function withChatMesh(fn) {
if (window.ChatMesh) return fn(window.ChatMesh);
document
.querySelector('script[data-chatmesh-key]')
.addEventListener('load', () => fn(window.ChatMesh), { once: true });
}
// Open the chat from your own button
document.getElementById('help').addEventListener('click', () => {
withChatMesh((cm) => cm.open());
});
// Pre-fill the visitor's details once they've signed in
withChatMesh((cm) => cm.identify({ name: 'Ada Lovelace', email: 'ada@example.com' }));
// Close it programmatically, e.g. on route change
// withChatMesh((cm) => cm.close());
</script> Webhooks
Available on Startup and Enterprise. Under Settings → Integrations → Webhook, enter an https:// URL. ChatMesh shows a signing secret once — store it safely. We then send a POST with a JSON body for these events:
ticket.created— a visitor started a new conversation.message.created— a new message was added (includesmessage).ticket.updated— status, priority, assignee or subject changed.
POST /webhooks/chatmesh HTTP/1.1
Content-Type: application/json
X-ChatMesh-Signature: t=1767225600,v1=5f2b6c…e91a
{
"type": "message.created",
"ticket": { "id": "…", "number": 1042, "status": "open", "priority": "normal", … },
"message": { "id": "…", "authorType": "visitor", "body": "Hi! Do you ship to Ireland?", "internal": false, "via": "widget", … }
} Every request carries an X-ChatMesh-Signature header of the form t=<unix>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> using your secret. Always verify it before trusting the payload:
// server.mjs — Node 18+, Express 4/5
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.CHATMESH_WEBHOOK_SECRET; // shown once when you set the webhook URL
const TOLERANCE_SECONDS = 300;
function verifyChatMeshSignature(header, rawBody) {
// Header format: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
const parts = Object.fromEntries(
String(header || '').split(',').map((kv) => kv.trim().split('=')),
);
const t = Number(parts.t);
if (!parts.v1 || !Number.isFinite(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false; // replay protection
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${parts.t}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(parts.v1, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Use the RAW body — re-serialised JSON will not match the signature.
app.post('/webhooks/chatmesh', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyChatMeshSignature(req.get('X-ChatMesh-Signature'), req.body)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204); // acknowledge quickly, then do the work
if (event.type === 'ticket.created') {
// e.g. kick off your AI agent, notify on-call, create a CRM record…
}
});
app.listen(3000); You’re all set. Questions? Email support@chatmesh.io or see the API overview.