Skip to content

Quickstart

Your server proves who the user is by signing a short-lived token with the app’s server secret. The browser only ever sees that token, never the secret.

  • A mondochat account. Staff create it on request: tell them your company name and the email of the person who will own it. That email gets a notice titled “You have access to your company on mondochat”, and the owner signs in at https://dash.mondochat.io with a link sent to the same email, whenever they like. See Tenants and apps.
  • A server that can sign an HS256 token for the user who is logged in, in Node, PHP, Python or any language with a JWT library.
  • A page of your site served over https from an origin you can list in the dashboard.

New accounts start in test mode, with up to 50 users per app. Ask staff to switch yours to live when you are ready.

  1. Get the app id and a server secret. Open Secrets in the dashboard and create one. It starts with mcs_, is shown once and belongs on your server only. The app id is in the dashboard URL, /apps/<app id>/..., and on the same page. You send it as Mondochat-App-Id.

  2. Allow your site’s origin. Open Origins and add every origin that shows the chat, for example https://www.example.com or https://*.example.com. The chat loads only inside pages from listed origins. Every origin is https://host[:port]: an http:// origin, http://localhost included, answers 422 when you save. The sample servers below listen on plain http (port 8081), so put TLS in front of them and list the https address you open. For a local try, make a self-signed certificate and a small proxy:

    Terminal window
    openssl req -x509 -newkey rsa:2048 -nodes -days 30 -keyout key.pem -out cert.pem -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

    Save this as tls-proxy.mjs next to the two files and run node tls-proxy.mjs while the sample server runs:

    import { readFileSync } from 'node:fs'
    import { request } from 'node:http'
    import { createServer } from 'node:https'
    createServer({ key: readFileSync('key.pem'), cert: readFileSync('cert.pem') }, (req, res) => {
    const up = request({ port: 8081, path: req.url, method: req.method, headers: req.headers }, (r) => {
    res.writeHead(r.statusCode, r.headers)
    r.pipe(res)
    })
    req.pipe(up)
    }).listen(8443)

    List https://localhost:8443 as the origin, open that address, and accept the certificate warning once: the browser must trust the certificate before the page and the chat load. With mkcert installed, mkcert localhost 127.0.0.1 makes a trusted pair instead (use its localhost+1.pem and localhost+1-key.pem files).

  3. Optional: set your own roles and policy. Open App in the dashboard. The defaults have one role, member, and members may message each other but not call, which is enough for this quickstart. For fans and models, or any other roles, see Settings overview. Omitted keys keep their defaults.

  4. Add a token endpoint to your server. The chat calls getToken whenever it needs to sign in, on load and again after a session ends. Your endpoint answers with a token for the logged-in user, or an empty body when nobody is logged in.

    The token is an HS256 JWT signed with the server secret:

    Claim Required Value
    iss yes your app id
    sub yes the user’s id on your site, 1 to 128 chars of A-Z a-z 0-9 . _ : @ -; do not start it with guest:
    name yes display name
    iat, exp yes seconds; exp at most 600 after iat (30s clock skew allowed)
    role no a role key from your settings; default default_role
    avatar no https URL
    lang no it, en, es or fr
    perms no per-user overrides of policy flags and limits, for example {"call": false}
    peer no the external id this user was sent to talk to; counts as a vouch, see Embed
    jti no when present, the token is single use

    Each example below is a whole small site: the token endpoint, the page with the embed snippet, and a stand-in login (/login?as=alice) that you replace with your own sessions. Any JWT library works too, as long as the algorithm is HS256.

    Save it as server.mjs. It has no dependencies.

    import { createHmac, randomUUID } from 'node:crypto'
    import { createServer } from 'node:http'
    const APP_ID = process.env.MONDOCHAT_APP_ID
    const SECRET = process.env.MONDOCHAT_SERVER_SECRET
    const EMBED = process.env.MONDOCHAT_EMBED ?? 'https://app.mondochat.io/embed.js'
    const sessions = new Map()
    const b64url = (s) => Buffer.from(s).toString('base64url')
    function mondochatToken(user) {
    const iat = Math.floor(Date.now() / 1000)
    const claims = { iss: APP_ID, sub: user.id, name: user.name, iat, exp: iat + 300 }
    const body = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(JSON.stringify(claims))}`
    return `${body}.${createHmac('sha256', SECRET).update(body).digest('base64url')}`
    }
    const page = `<!doctype html>
    <meta charset="utf-8"><title>My site</title>
    <form onsubmit="MondoChat.open({ with: this.peer.value }); return false">
    <input name="peer" placeholder="user id"> <button>Chat</button>
    </form>
    <script>
    window.MondoChat = window.MondoChat || function () { (MondoChat.q = MondoChat.q || []).push(arguments) };
    MondoChat('init', { appId: '${APP_ID}', getToken: async () => (await fetch('/mondochat-token')).text() || null });
    </script>
    <script async src="${EMBED}"></script>`
    createServer((req, res) => {
    const url = new URL(req.url, 'http://localhost')
    const user = sessions.get(/(?:^|; )sid=([\w-]+)/.exec(req.headers.cookie ?? '')?.[1])
    if (url.pathname === '/login') {
    const id = (url.searchParams.get('as') ?? '').toLowerCase().replace(/[^a-z0-9]/g, '')
    const sid = randomUUID()
    if (id) sessions.set(sid, { id, name: id[0].toUpperCase() + id.slice(1) })
    res.writeHead(303, { location: '/', 'set-cookie': `sid=${sid}; Path=/; HttpOnly; SameSite=Lax` }).end()
    } else if (url.pathname === '/mondochat-token') {
    res.writeHead(200, { 'content-type': 'text/plain', 'cache-control': 'no-store' })
    res.end(user ? mondochatToken(user) : '')
    } else {
    res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }).end(page)
    }
    }).listen(8081, '0.0.0.0')
  5. Run your site. Set MONDOCHAT_APP_ID and MONDOCHAT_SERVER_SECRET in its environment and serve it over https on an origin you listed in step 2. The examples read the script address from MONDOCHAT_EMBED and default to https://app.mondochat.io/embed.js.

  6. Add the embed snippet to every page that shows the chat. The examples above already hold it:

    <script>
    window.MondoChat = window.MondoChat || function () { (MondoChat.q = MondoChat.q || []).push(arguments) };
    MondoChat('init', { appId: 'APP_ID', getToken: async () => (await fetch('/mondochat-token')).text() || null });
    </script>
    <script async src="https://app.mondochat.io/embed.js"></script>

    A launcher bubble appears in the corner once getToken returns a token. Call MondoChat('logout') when the user logs out of your site. Every option, method and event is on Embed.

  7. Open a chat between two users. Open https://localhost:8443/login?as=alice in one browser profile and https://localhost:8443/login?as=bob in another, or in a private window. The sample session cookie is shared within a profile, so two normal windows of one profile stay the same user. As Alice, type bob and press Chat. The button calls MondoChat('open', { with: 'bob' }), where with is the other user’s id on your site.

    A user must exist before others can open a conversation with them. A user exists after their first session, or you create them from your server right away:

    Terminal window
    curl -X PUT https://app.mondochat.io/v1/server/users/bob \
    -H "Authorization: Bearer $MONDOCHAT_SERVER_SECRET" -H "Mondochat-App-Id: $MONDOCHAT_APP_ID" \
    -H 'content-type: application/json' -d '{"name": "Bob", "role": "member"}'

    MondoChat('open') without with opens the inbox. The request is Upsert a user.

  • Alice sends a message and Bob sees it arrive.

  • To check a token by hand, exchange it. A refused token answers 401 with the reason in detail:

    Terminal window
    curl -X POST https://app.mondochat.io/v1/session -H 'content-type: application/json' \
    -d '{"app_id": "<app id>", "token": "<token>"}'
  • The Test console in the dashboard opens the chat as any user and role without a token endpoint. Two browser windows with two users are the quickest way to try messages and calls.

Symptom Cause
Nothing appears, the console shows a frame-ancestors error The page is not on a listed origin.
POST /v1/session answers 401 with detail The token was refused. detail says why: signature, exp must be at most 600s after iat, bad header and so on. Check that iss is the app id and that the secret is active.
401 guests are disabled getToken returned nothing and guests.enabled is off: nobody is logged in.
403 test_user_limit Tenants in test status accept 50 users per app.
Opening a chat with bob fails, POST /v1/conversations answers 404 Bob has never signed in and was not created with PUT /v1/server/users/bob.
  • A client token lives at most 600 seconds.
  • An app has at most 2 active server secrets and 20 origins, and the origins together take at most 1024 bytes.
  • A tenant in test status accepts at most 50 users per app.
  • The session exchange is limited to 300 per IP per minute.