Getting started

Go from zero to answering live chats in about ten minutes. Follow the steps in order, or jump straight to the part you need.

Create your account

  1. Go to /signup.
  2. Enter your name, email, a password of at least 10 characters and a name for your workspace (usually your company or product name).
  3. 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:

Your snippet
<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

index.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:

functions.php
<?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.

  1. In Shopify admin, go to Online Store → Themes.
  2. Next to your current theme, click … → Edit code.
  3. Under Layout, open theme.liquid.
  4. Find the closing </body> tag (use Ctrl/Cmd + F), paste the snippet just above it and click Save.
layout/theme.liquid
<!-- … 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 Router — app/layout.tsx
// 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 Router — pages/_app.tsx
// 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
<!-- 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
// 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:

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.
  • www and non-www are 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

  1. Open Settings → Team and click Invite.
  2. 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.
  3. 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.

  1. In the portal, go to Settings → Integrations and click Add to Slack.
  2. Slack asks you to approve the ChatMesh app for your workspace. Click Allow and you’ll be returned to the portal.
  3. Choose the channel where new chats should appear by pasting its channel ID, then save.
  4. If it’s a private channel, invite the app first by typing /invite @ChatMesh in that channel.

How to find a Slack channel ID

  1. Open the channel in Slack (desktop or web).
  2. Click the channel name at the top of the conversation.
  3. 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.

  1. In Settings → Integrations, click Invite bot under Discord. Choose your server and authorise the bot.
  2. 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.
  3. Enable Developer Mode in Discord: User Settings → Advanced → Developer Mode.
  4. Right-click (or long-press on mobile) the channel and choose Copy Channel ID.
  5. 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

  1. Go to Settings → AI agents and click Create key.
  2. Give it a clear name, such as Support AI — this is shown as the author of its replies.
  3. 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)

Terminal
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.

claude_desktop_config.json
{
  "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

ToolWhat it does
list_ticketsFind open, pending or closed tickets, filtered by assignee or search text.
get_ticketRead a ticket with its full message history, including internal notes.
reply_to_ticketSend a reply the visitor sees instantly in the widget.
add_internal_noteLeave a note for human teammates that visitors never see.
update_ticketChange status, priority, assignee or subject — e.g. hand off to a human.
get_statsOpen 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.

System prompt
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).

Terminal
# 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.
Example
<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 (includes message).
  • ticket.updated — status, priority, assignee or subject changed.
Example delivery
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:

Verify signatures in Node.js
// 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.