Documentation overview

Site chat

A chat on your own website, in your colours: visitors write to you from any page, on a computer or a phone, and your team answers them in Helpdesk. It runs on the Website SDK script you already have, so there is nothing new to install – you design it, try it on your real site where only you can see it, and switch it on.

What your visitors get

  • A launcher in the corner of every page, or only the pages you choose, in your colour. It opens the chat panel. On a computer the panel opens above the launcher and the page stays usable; on a phone it fills the screen.
  • Your first words – a welcome line and up to three conversation starters a visitor can tap instead of typing.
  • An honest promise – a line saying how fast you usually reply, or, outside your opening hours, when you are back.
  • Your team's replies, signed with the team name or each person's first name, and "Anna is typing…" while someone writes.
  • A reply even after they leave. The answer waits in the chat for when they come back, and – when your brand has a support mailbox – reaches them by e-mail as well.
  • A rating when the conversation is resolved, if your brand asks for ratings (off until you turn it on, see Ratings under Behaviour): 1–5 and an optional comment. Then Start a new conversation.
  • Chat · History · Inbox. The panel's tabs: the conversation itself; for a customer your site signs in, their earlier conversations (see History for signed-in customers); and your inbox messages – offers, codes, order notices – when you have any. With the chat alone there is no tab bar.
  • Their own language. The chat speaks English, Czech, Slovak, German, French, Spanish, Italian, Portuguese, Polish and Dutch. It takes the language you choose in the studio; without one, your page's own language; on a page that does not say its language, or says one of the others, your brand's language; and only then English. Your own words – the welcome, the starters, the away message – appear exactly as you wrote them.

Visitors write up to 4,000 characters a message. Photos and PDFs only when you allow them – see Files visitors attach. The chat never opens by itself, plays no sound unless you switch one on, and carries no "Powered by" line. It works with a keyboard alone, Esc closes it, and screen readers announce new replies.

Off, Test and Live

Site chat has three states. You set them on the Website SDK page (the Site chat row) or in the studio's header.

  • Off – nothing on your site. Conversations already in Helpdesk stay there.
  • Test – only a browser that opened your site through the test link sees the chat. Everyone else sees nothing at all: their browser never even loads the chat.
  • Live – every visitor sees the design you published.

The test link is your site's address with ?aicmo_chat=test added – Test link in the studio, or the line under the Site chat row, with Copy and Open. It works on any page: add ?aicmo_chat=test to any address on your site. The tab stays in test mode while you click around, until you close it or open a page with ?aicmo_chat=off.

A test tab shows your latest draft, also once the chat is Live – so you can try a change on your real site before you publish it, while visitors keep the published chat. Conversations started in a test tab land in Helpdesk tagged test. They are answered by the AI exactly like real ones, so you can see what your customers would get, but your support employee never learns from them: their answers are not kept as earlier replies to look at, their questions never reach Questions it could not answer, they add no topic, and they do not count towards whether it believes it is ready for its next step.

The way on is Off, then Test, then Live. In the studio, Start testing switches the chat to Test and hands you the test link; once you have tried it, Go live publishes the draft you tested and switches the chat on for everyone. Picking Live straight from Off asks Go live without testing? first, and taking a live chat back to Test or Off asks too, because your visitors lose it – in Test only browsers with the test link keep it. A change of state reaches every visitor within a couple of minutes.

The Site chat row on the Website SDK page tells you where things stand: live – appears once the script is on yoursite.com until the script has been seen on your site, how many conversations arrived in the last 7 days, replies stay in the chat – no support mailbox, and opens from your own button when the launcher is hidden. Its Show section holds the code snippets below.

The studio

Design on the Site chat row opens the studio: your site, a preview, and the settings beside it, in three tabs – Design, Behaviour and Identity.

The preview is the real chat in your design, drawn over a picture of your own site. Switch Desktop | Phone and choose what it shows – Start, Conversation, Reply while closed, E-mail ask, Away, Rated, History, Inbox or Attachments. Click into a setting and the preview moves to the moment that setting changes. Attachments shows the paperclip, a thread with a photo and a PDF, and two files in the message box – one still uploading – even while files are off, and says that visitors do not see it yet. Rated does the same while your brand's ratings are off. History shows sample rows – what a signed-in customer sees; visitors who are not signed in never get that tab. With the Auto theme, Light | Dark shows both.

Every change is saved as a draft about a second later – the header says Saved 14:05. Nothing reaches visitors until you publish: in Live the status reads Live – changes not published, and More → Discard unpublished changes puts the draft back to what visitors see. If a teammate changed the same draft meanwhile, the studio stops saving and says who and when – reload to see their version.

The first time you open a site it starts from your Inbox design, then reads your site once – Match yoursite.com – for its colour, light or dark, its font and its corners. Each line names what changed and has Undo. Match never takes your site's logo. You can run it again from the toolbar.

Design

  • Colour – the launcher, the visitor's messages and the send button. Use my brand offers your brand's own colours. A colour that is light against your site gets an amber note: the launcher then carries a thin ring so it still shows, but a deeper shade reads better.
  • Theme – Light, Dark, or Auto, which follows each visitor's device. With Dark or Auto you can set a separate Colour in dark.
  • Corners – Square, Rounded or Round.
  • Font – Your site's font (a page without one gets the system font) or System.
  • Text size – Small, Medium, Large or Extra large: every line in the panel and the launcher's label. Medium suits most sites; a larger size helps an audience that reads small type with effort. Whatever you choose, the message box on a phone never goes below 16 px, so it never makes the page zoom in.
  • Launcher – Icon, Label (the icon and a few words – "Chat with us" in the visitor's language unless you write your own, up to 30 characters) or Your own button. The icon is a speech bubble or a question mark. A label shows on computers only, unless you switch on Show the label on phones – the round icon covers less of a small screen.
  • Side – Right or Left.
  • Header – the Team name (your brand name to start with, up to 40 characters), your brand's Logo (on a dark panel the team name shows in type instead), and Replies signed by – the team name or each person's first name. Replies the AI writes always carry the team name.
  • First words – the Welcome (up to 280 characters; "Hi – how can we help?" in the visitor's language when empty) and up to three Conversation starters of up to 40 characters each. A tap sends the starter as the visitor's first message.

Behaviour

  • Reply expectation – Automatic, Minutes, Hours, A day or Say nothing. Automatic says "a few minutes" when your support employee answers chats on its own; otherwise it reads how fast your team answered chats in the last 14 days, and says nothing until 5 chats have been answered. The studio shows what Automatic currently resolves to.
  • Opening hours – Any time, or Set hours: a time zone (your browser's own to begin with) and up to three ranges a day. Outside the hours: Keep the chat – it says Away – back Monday at 9:00 and visitors can still write – or Hide the launcher. Someone already in a conversation always keeps the chat.
  • Away message – up to 280 characters, shown while you are away. Empty uses a short default in the visitor's language.
  • Visitors who leave – Ask for an e-mail (After the first message, Before chatting, or Don't ask) and E-mail replies they have not seen. See Visitors who leave.
  • Files – Visitors can attach files: photos and PDFs up to 10 MB, three to a message. Off until you switch it on. While it is on and the support employee cannot read them yet, the steps that are left show under the switch. See Files visitors attach.
  • Ratings – Ask customers to rate the conversation: one question, 1–5, after you resolve a conversation, by e-mail for e-mail conversations and in the chat window here. Off until you turn it on. It is the brand's setting, the same switch as in Connections › Support inbox, so it saves at once and does not wait for Publish. Shown only on the site's own brand.
  • Where it shows – Pages: Every page, Only on, or Not on – one path per line, where * matches anything (/help/*). Try a page checks a path against your rules. Devices: Desktop and phone, Desktop, or Phone. These rules decide where the launcher appears; your own buttons and links open the chat on any page.
  • Inbox – appears when the site has inbox messages. With Site chat on, your inbox messages sit in the chat panel in a tab of their own with its own unread count, and the launcher's badge counts both. Inbox messages in the panel – Show or Hide – and the tab's Label, up to 24 characters; empty reads your Inbox title, or "Inbox" in the visitor's language.

Optional

Folded away at the foot of Behaviour, for what most sites never touch:

  • Spacing from the edge – from the side and from the bottom, set separately for computers and phones (switch Desktop | Phone above the preview).
  • Launcher size – Small · 48, Medium · 56 or Large · 64 pixels.
  • On phones – Full screen or Tall sheet.
  • Back button – Leaves the page (the default) or Closes the chat. See On phones.
  • Sound on a new reply – a soft sound when a reply arrives while the visitor's tab is in the background. Off by default.
  • Language – one of the ten, or The page's language: the page's own language, then your brand's, then English.
  • Privacy line – your own words under the message box, with a link if you give one. Nothing shows while it is empty, and we never add privacy or consent wording of our own.
  • Sites that may show the chat – empty means your site's domain and its subdomains; list other domains here, or Accept the chat from any site. This limits where the chat can be embedded, not who can call it.
  • Stacking order – one below the SDK's own website messages by default. Raise it only if something on your page covers the launcher.
  • AI turns a day – how many times a day the AI may answer this site's chats (1,000 to start with). An answer is one turn however many quick lines it answers. Past it, new messages wait for a person and the desk says why.

Identity

How your site's logged-in customers reach the chat, and the signing that makes them known (see Signed-in customers). The tab says it in one line, from what reached the chat in the last 7 days – for example shop.example names logged-in customers without a signature, so chats show them as not verified., shop.example sends a signature that doesn't match, so chats show customers as not verified., or, with Require signed identities on, … so chats treat them as anonymous. When a signature did not match, the line under it says why:

  • The signature isn't 64 hex characters. Send the HMAC-SHA256 as hex.
  • Sign the lowercased e-mail (or customer ID) with this site's secret.
  • The chat signature has expired or was made for another site.

Below are Require signed identities, the site's identity secret (Reveal, Copy, Rotate) and Chat across devices – the same as under Identity verification on the Website SDK page. They are saved straight away, not with Publish, and only an administrator of the brand can change them (see Changing the identity secret); anyone else sees why. While the chat is in Test or Live and customers arrive unsigned or with a signature that does not match, a line over the preview says so, with Set up signing (or Fix signing) to open this tab. It never stops a publish. Set up signing in Helpdesk opens it too.

Publishing, and who can

The studio's one solid button follows the state: Start testing while the chat is Off, Go live in Test – it publishes the draft and switches the chat on – and Publish in Live, available when the draft differs from what visitors see. Anything that would make the chat appear nowhere is listed under Before it can go live and stops the publish – "Only on" with no pages, both devices off, or opening hours with every day closed while the launcher hides outside them. Your own button publishes with a reminder to add data-aicmo-chat to it.

Anyone who can open the brand the site belongs to can design and publish its chat. Support seats see the setup read only – Site chat is set up by an administrator – and answer the conversations in Helpdesk. A brand the site is shared into sees Managed on its home brand, and changes it there.

On phones

Phones get a chat made for them, not a shrunken desktop panel:

  • Full screen (the default) covers the screen and keeps clear of the notch and the home bar. Tall sheet rises from the bottom over a dimmed page; a tap on the page closes it.
  • The page behind stays where it was – it does not scroll under the chat – and comes back exactly as the visitor left it.
  • The message box sits above the keyboard and never makes the phone zoom in. Every button is large enough for a thumb, and Enter starts a new line – Send sends.
  • The launcher sits above the phone's home bar, at its own distance from the edge (Spacing from the edge for phones).
  • Esc closes the chat everywhere, and Android's back button closes it where the browser allows. Back button → Closes the chat makes back close it on every phone by adding a step to the browser's history while the chat is open – some site frameworks dislike that, so it is off by default.
  • A page without a mobile layout still gets a chat at the phone's real size.

Visitors who leave

A visitor who closes the tab before you answer still gets your reply.

In the chat. The conversation is kept in their browser. When they come back to your site, the reply is there, with a count on the launcher; on a computer a short preview of the reply appears above the launcher. A browser forgets a conversation 30 days after its last message.

By e-mail. With E-mail replies they have not seen on, a reply the visitor has not seen a few minutes after you sent it goes to them by e-mail, from your brand's support mailbox. A reply counts as seen once it was on screen in the open chat while the visitor was using the page – if they are still reading, nothing is sent. The e-mail carries your replies and never quotes what the visitor wrote; it ends with Not you? Stop these e-mails, which stops reply e-mails from this brand's chat to that address. When the visitor answers the e-mail, the answer lands in the same conversation in Helpdesk. At most one such e-mail goes out per conversation every 30 minutes, three a day to one address, and 200 a day per site.

Where the address comes from decides what the e-mail offers:

  • A customer signed in by e-mail (see Signed-in customers) is known to be who they say, and the e-mail goes to that address. On a site that is Live, it also carries Continue in the chat, which reopens the conversation on your site while they are signed in there.
  • A customer signed in by your own customer ID has no address the chat knows: like any visitor, they are asked for one (unless you chose Don't ask), and their e-mail goes where they typed it – as below.
  • A customer your site named without a signature is not known to be who your site says, so that address gets nothing: like any visitor, they are asked for one, and their e-mail goes where they typed it.
  • On a site in Test, the e-mail carries no link into the chat, signed-in customer or not – only Reply to this e-mail – because a tab opened from an e-mail is not a test tab.
  • An address the visitor typed is a place to reply to, nothing more – nobody checked that it is theirs. The chat asks Where should we reply if you leave? after the first message (or before the first, if you chose Before chatting). The e-mail to a typed address says Reply to this e-mail and carries no link into the chat. A typed address never attaches the conversation to a customer record. Once the owner of that address answers by e-mail, the conversation continues by e-mail only – your replies go to that address at once – and the chat where the address was typed shows This conversation continues by e-mail.

The e-mail goes out only when your brand has a support mailbox that can send on its own – a hosted support address, your own mail server (SMTP), or your brand's own e-mail sending (Connections → Support inbox). Without one, replies stay in the chat: the studio says Visitors who leave get your reply by e-mail once {your brand} has a support mailbox and offers Connect a support mailbox, and the chat does not ask visitors for an address.

Files visitors attach

With Behaviour → Files → Visitors can attach files on, a visitor can show you what they mean – a screenshot of an error, a photo of a parcel, an invoice. It is off until you switch it on, and like every setting it reaches visitors when you publish; a test tab tries it from the draft.

What they can send. Photos – JPEG, PNG, WebP, and HEIC from an iPhone – and PDFs, up to 10 MB each and three to a message. A message may be files alone. What a file is comes from its content, never from its name: a file called invoice.png that is really a PDF arrives as a PDF named invoice.png.pdf, and anything else – other image types, SVG, web pages, archives – is refused.

How it looks to them. A paperclip in the message box, Attach a photo or PDF (large enough for a thumb on a phone). On a computer they can also paste a screenshot or drop files on the chat – Drop files to attach. Each file uploads the moment it is picked, with its progress, and × takes it off. Send waits while a file is still uploading. In the conversation a photo shows as a picture and a PDF as its name; a tap opens it full size in a new tab. The chat says what went wrong in the visitor's language: Larger than 10 MB, Only photos and PDFs, Up to 3 files per message, Can’t upload right now – try again later, or Upload failed with Retry when the network or our side failed.

Where files live. Privately, never at a public address. The visitor sees their own files in their conversation, and your team opens them in Helpdesk – anyone who can work the conversation, support seats included. Both open them through short-lived links that work for ten minutes. A file belongs to the conversation it was sent in: kept with it and deleted with it. A file uploaded but never sent is deleted about an hour later. A data request's export includes the files of the chats it gives in full, each with a link that works for 7 days; an erase deletes them with the conversations. Both reach the chats signed in with the person's e-mail or customer ID – the one on their record, or the one the request names when there is no record – and the chats their record is attached to. A chat where your site named the person without a signature is not theirs – anyone could have named them – so their export shows only that they were named: the conversation, when, and the line that says so, never the visitor's messages. An erase takes their name off the conversation and out of that line, and keeps the conversation.

What the AI does with them. Nothing, until your organisation's administrator confirms version 1.3 or later of the AI Support Agent Addendum in Settings › Agents (the button names the newest version: Confirm version 1.4 since 12 October 2026, which covers this too) – reading what visitors attach is new in 1.3, and everything you confirmed before keeps working without it. Until then the AI is told only that a photo or a PDF came – not its name, its size or anything in it – and where the answer depends on it, it says a colleague will look at it. Once 1.3 is confirmed, and wherever the support employee works the brand:

  • a photo is read as it is, by a model that reads images. It is converted first, which removes what a camera stores with it, such as where it was taken. A name or a number in a picture cannot be replaced with a placeholder the way it is in text, and the AI is told never to copy a personal detail it can only see in a picture into its reply;
  • a photo in the HEIC format – what an iPhone saves when a photo is dragged or pasted in rather than picked – is kept and shown to your team, but the AI does not read it: it is told only that a photo came that it cannot see;
  • a photo cannot be checked for a regulated topic before a model reads it, so the model that reads it is told to stop and write nothing when a photo shows self-exclusion, gambling harm, self-harm, a chargeback or legal action – the AI is then paused on the conversation exactly as for such a message. This depends on the model recognising the picture;
  • a PDF is read by its text, which is treated exactly like a message: the identifiers in it are replaced before a model reads it, and one that touches a regulated topic pauses the AI on the conversation. A PDF is read once: the text read from it is kept with the file and deleted with it;
  • on a conversation where the AI is paused, nothing is read at all.

Reading a file does not change what a reply costs.

Limits. One browser can send 10 files an hour and 50 a day, one network 30 an hour and 300 a day, and one site 500 a day. Past a limit the visitor sees Can’t upload right now – try again later (it does not blame them: a network or the site may have reached its limit, not this visitor) and can still write. One network uploads at most three files at the same time, and an upload that stalls, or takes longer than two minutes, is stopped; either way the visitor sees Upload failed with Retry.

On a site with a Content Security Policy, files go to an address of their own – see Content Security Policy.

Signed-in customers

When your site knows who a visitor is – they are logged in – the chat can know too, and the conversation arrives in Helpdesk with that customer's record beside it. Sign every identify() call: your server computes an HMAC-SHA256 of the customer's e-mail or your own customer ID, in lower case, with the site's Identity secret (Reveal or Copy under Identity verification on the Website SDK page, or in the studio's Identity tab), as hex – never in the browser – and the page passes it along:

js
// on every page while the customer is logged in
AICMO.push(['identify', 'player-48213', { hash: SIGNATURE }]);
// or for the chat alone – this is also how to sign the chat in when the
// visitor allowed support but not marketing (see Consent)
AICMO.chat('identify', { external_id: 'player-48213', hash: SIGNATURE });
// on logout
AICMO.reset();

E-mails and customer IDs are compared in lower case, so a customer ID must belong to one person whatever its letter case – your customer number, never a username the customer chose.

A customer the SDK identifies from your identity cookie (Data → Events) is never signed – the cookie carries no signature – so with the cookie alone they arrive as not signed, below. Call identify() with { hash } as well to have their chats verified.

While the chat is on, a page that names a customer without a signature says so once in the browser's console: [AI CMO] Site chat cannot verify this customer – pass { hash }, signed on your server. The browser cannot tell a signature that does not match – the studio's Identity tab does.

What the chat does with who your site says the visitor is:

  • Signed – the identity is verified: the conversation is that customer's, with their record beside it in Helpdesk.
  • Not signed, or a signature that does not match – with Require signed identities off, the chat keeps it as a claim: Helpdesk shows it labelled not verified and asks your team to confirm who the visitor is before discussing their account. A claim never attaches a customer record, never reaches your warehouse or journeys, is never used for account lookups, gets no reply e-mail, no Continue in the chat, no History and nothing on other devices – anyone who loads your page can name anyone. Its one use is for your team: Helpdesk offers Open their record, a link to the customer record the name matches, to check against – never attached. It is kept only when marketing is allowed for the visitor, as for identify() itself. With Require signed identities on, it is ignored and the chat stays anonymous.

Logging in during a chat. A visitor who is chatting and then logs in on your site is attached to the conversation within seconds, on the chat's next check – no new message needed, and the whole conversation stays. Helpdesk shows it as a line in the thread: Customer logged in as jane@acme.com – identity verified, or – not verified when your site did not sign it. Only this browser's own conversation is attached this way, only while it is open, and only when the visitor wrote in it within the last hour, so the next person to log in at a shared computer never inherits an older chat; an older one becomes theirs with their next message. The same line also opens every chat a customer starts while already logged in, and comes before the message that attaches an older chat.

Three rules keep one person's conversation away from the next person at the same computer:

  • Identify on every page while the customer is logged in. The chat trusts only an identity given on the page it is running on, never one remembered from an earlier page.
  • Call AICMO.reset() on logout. It forgets who the browser was – the stored identity, its signature, the last value read from your identity cookie and the chat with them – and, when someone had been identified, also gives the browser a fresh visitor ID, starts a new anonymous person in your visitor analytics and clears their inbox messages from the page. The next person starts clean.
  • A signed-in conversation opens only for that customer. If it is opened without the same signed identity on the page, the chat says Sign in to see this conversation and offers Start a new conversation – a page that names the customer without a signature counts as no identity here, which is another reason to sign every identify() call.

An identifier passed without a valid signature never binds the conversation to a record: with Require signed identities off it is shown as not verified, as above; on, it is ignored. A fact about a customer's own account that the support employee looked up goes back to the chat without a person only when the message asking for it was signed – a customer who logs in during a chat gets one from their next message on.

Their chat on every device

A conversation lives in the browser it started in. To let a signed-in customer start on their laptop and carry on from their phone, your server adds a second signature that is only for the chat, chat_sig, and when it stops working, exp:

  • chat_sig is the HMAC-SHA256, as hex, of aicmo-chat:v1|<site key>|email|<the e-mail in lower case>|<exp> with a key of its own: the SHA-256 of aicmo-chat-key:v1| followed by the identity secret, as its raw 32 bytes – never the identity secret itself, so no hash your server hands out can ever be a chat_sig. For your own customer ID, write external_id in place of email – in the signed text too – and the ID in lower case.
  • exp is a time in Unix seconds, at most 24 hours ahead. Sign afresh on every page load.

Chat across devices under Identity verification (Show) has the function in Node, PHP and Python, with your site key filled in where these say YOUR_SITE_KEY:

js
// On your server, never in the page. AICMO_IDENTITY_SECRET = the identity secret.
const crypto = require('crypto');

function aicmoChatIdentity(email) {
  const secret = process.env.AICMO_IDENTITY_SECRET;
  const typed = email.trim();
  // A look-alike character (a Kelvin sign for a K) is never signed: null = not signed in.
  if (typed.normalize('NFKC') !== typed) return null;
  const value = typed.toLowerCase();
  const exp = Math.floor(Date.now() / 1000) + 12 * 3600; // at most 24 hours ahead
  // chat_sig has a key of its own: SHA-256 of 'aicmo-chat-key:v1|' and the secret, raw bytes.
  const chatKey = crypto.createHash('sha256').update('aicmo-chat-key:v1|' + secret).digest();
  const sign = (key, text) => crypto.createHmac('sha256', key).update(text).digest('hex');
  return {
    email: value,
    hash: sign(secret, value),
    chat_sig: sign(chatKey, `aicmo-chat:v1|YOUR_SITE_KEY|email|${value}|${exp}`),
    exp,
  };
}
php
<?php
// On your server, never in the page. AICMO_IDENTITY_SECRET = the identity secret.
function aicmo_chat_identity(string $email): ?array {
    $secret = getenv('AICMO_IDENTITY_SECRET');
    $typed = trim($email);
    // A look-alike character (a Kelvin sign for a K) is never signed: null = not signed in.
    // Past ASCII this check needs the intl extension; without it, such an e-mail is not signed.
    if (!mb_check_encoding($typed, 'ASCII')
        && !(class_exists('Normalizer') && Normalizer::isNormalized($typed, Normalizer::FORM_KC))) {
        return null;
    }
    $value = mb_strtolower($typed, 'UTF-8');
    $exp = time() + 12 * 3600; // at most 24 hours ahead
    // chat_sig has a key of its own: SHA-256 of 'aicmo-chat-key:v1|' and the secret, raw bytes.
    $chatKey = hash('sha256', 'aicmo-chat-key:v1|' . $secret, true);
    return [
        'email' => $value,
        'hash' => hash_hmac('sha256', $value, $secret),
        'chat_sig' => hash_hmac('sha256', "aicmo-chat:v1|YOUR_SITE_KEY|email|$value|$exp", $chatKey),
        'exp' => $exp,
    ];
}
python
# On your server, never in the page. AICMO_IDENTITY_SECRET = the identity secret.
import hashlib, hmac, os, time, unicodedata

def aicmo_chat_identity(email):
    secret = os.environ["AICMO_IDENTITY_SECRET"].encode()
    typed = email.strip()
    # A look-alike character (a Kelvin sign for a K) is never signed: None = not signed in.
    if unicodedata.normalize("NFKC", typed) != typed:
        return None
    value = typed.lower()
    exp = int(time.time()) + 12 * 3600  # at most 24 hours ahead
    # chat_sig has a key of its own: SHA-256 of "aicmo-chat-key:v1|" and the secret, raw bytes.
    chat_key = hashlib.sha256(b"aicmo-chat-key:v1|" + secret).digest()
    sign = lambda key, text: hmac.new(key, text.encode(), hashlib.sha256).hexdigest()
    return {
        "email": value,
        "hash": sign(secret, value),
        "chat_sig": sign(chat_key, f"aicmo-chat:v1|YOUR_SITE_KEY|email|{value}|{exp}"),
        "exp": exp,
    }

The page hands what your server returned to the chat:

js
// In the page, on every page while the person is signed in:
AICMO.chat('identify', { email, hash, chat_sig, exp });

What follows them. When a browser with no conversation of its own opens your site with a valid chat_sig, the chat picks up that customer's newest open conversation on this site that was signed in with the same e-mail or customer ID and had a message in the last 30 days – never one that now continues by e-mail. A test tab picks up only test conversations, and a normal visit only real ones. The hash alone never does this: it signs the customer in on the page, but opens only conversations this browser started. A conversation they started before signing in, and wrote in within the last hour, becomes theirs within seconds of signing in on that browser – no new message needed – and shows on their other devices from then on; what was written before stays on the device it started on. (An older one becomes theirs with their next message – see Signed-in customers.) Once exp has passed, the chat stops following them until a fresh chat_sig arrives; the hash still signs them in.

History for signed-in customers

A customer your site signs in with chat_sig – the signature in Their chat on every device – also gets a History tab in the chat: their earlier conversations on this site, from any device they used, up to 20, newest first. Each row shows the first message they wrote in it (or its date, when that message was files alone), when the last message they can see came, and whether it is Open, Closed or Continued by e-mail. A tap opens it read only – their messages, your team's replies and the files in them, with Back to the list – and there is no message box there, even on one still open: a new question goes in the Chat tab. A test tab lists only test conversations, and a normal visit only real ones.

History shows what the customer could already read in their chat: never your team's internal notes or the AI's unsent drafts, and a conversation that continues by e-mail ends where it moved. A row's title is always the customer's own words – renaming a conversation in Helpdesk never changes it. The conversation already in the Chat tab is not listed again, so the tab appears once there is at least one other – after the panel has read the list, the first time it opens on a page.

It lasts as long as the signature. Once exp has passed, after AICMO.reset(), or on a page that identifies the customer without a chat_sig, History is gone until a fresh signature arrives. The plain hash never shows it, and anonymous visitors never see it. A conversation begun before anyone signed in becomes the customer's from the moment they sign in during it, and History shows it from that moment on – what was written before, by them or by whoever used the browser earlier, stays on that browser, and a row with nothing of theirs since shows its date as its title. Nothing of History is stored in the browser: the list is read when the panel opens and held only by the page.

To open the panel on it from your own code, AICMO.chat('open', 'history'); AICMO.chat('open', 'inbox') opens it on your inbox messages.

Changing the identity secret

Under Identity verification, each site's secret has Reveal, Copy and Rotate. Rotate gives the site a new secret, and the current one keeps working for 7 days, so your server can switch to the new one first – the card says until when: The previous secret keeps working until …. After that, anyone your server still signs with the old secret counts as not signed in: personal messages and personalization fall back to what everyone sees, Site chat shows them to your team as not verified (as anonymous with Require signed identities on), and their chat stops following them to other devices.

Only if the secret leaked, Rotate and stop it now stops the old one at once: until your server signs with the new secret, everyone it signs counts as not signed in. Rotating again within the 7 days stops the secret you replaced the first time at once, and the confirmation says so.

Only an administrator of the brand the site belongs to can see and rotate its secret, or switch Require signed identities – its creator, an administrator of your organisation, or a teammate who can edit the brand; not a teammate who can only view it, not a brand the site is shared into, and not a support or data seat. Whoever holds the secret can sign any of your customers into their chat on any device, so each reveal, each rotation and each change of Require signed identities is listed in the admin portal's Audit Logs.

The chat follows the same consent your banner gives the rest of the SDK, with one category of its own: support.

  • Not stated, support follows marketing. On a site in consent mode, a visitor who allowed marketing gets the chat; one who did not, does not. On a site without consent mode, the chat works for everyone (unless the visitor turned the SDK off with optOut()).
  • Your site can give the chat on its own. Pass AICMO.push(['consent', { support: true }]) – from your banner, or from your own page code – and the visitor gets the chat with nothing else: no tracking, no identify() values, nothing sent to journeys or your warehouse. A signed chat identity – AICMO.chat('identify', …), see Signed-in customers – can still tell the chat who a logged-in customer is, so their conversation arrives with their record. An unsigned one is not used under support alone: a claim needs marketing. support: false takes the chat away. consent(null) – no choice on record – means no chat too, unless the page passes { support: true } after it.
  • Support merges with the rest. A consent call that leaves support out keeps what the page said about it earlier, and a call that carries only support leaves analytics and marketing exactly as they were. Like the other categories, pass it on every page load.

How the support category maps to your banner is your decision. You might give it with your necessary items, with a category of its own, or from a "Chat with us" button that asks first – we do not decide that for you, and the chat adds no consent wording of its own.

Before support is allowed, nothing chat-related is fetched or stored. A click on your chat button, #chat or AICMO.chat('open') opens nothing and fires the blocked event instead, so your site can show its banner:

js
AICMO.push(['chat', 'on', 'blocked', function () { openMyCookieBanner(); }]);

Use a <button data-aicmo-chat> for this. A link (<a href>) with data-aicmo-chat fires blocked too, but until support is allowed it also goes to its address as usual, so the visitor may have left the page before your banner shows. A link to #chat stays in the address until the chat is on, so a second click on the same link is not heard.

A chat asked for while the visitor's choice is still pending opens as soon as support is allowed.

What the chat carries. With support alone: the visitor's messages, an e-mail address if they give one for replies, the page they wrote from (the path only – no query or anchor), whether it is a phone or a computer, their language and time zone – and, when your page signs one, the customer's e-mail or customer ID (see Signed-in customers). With marketing allowed as well, also the site that sent them (its host only), the page title and their visitor ID, so their visit history joins the conversation, and – unless Require signed identities is on – an e-mail or customer ID your page names without signing it, kept as a claim labelled not verified. Your team sees these beside the conversation in Helpdesk.

When support stops being allowed, the chat closes and what it kept in the browser is deleted. With marketing refused and support allowed, the chat works but your inbox messages do not appear in its Inbox tab – they belong to marketing.

Opening it from your site

The launcher needs no code – the same script draws it. To open the chat from your own buttons and links:

No code. Any element with data-aicmo-chat opens the chat; data-aicmo-chat="toggle" opens and closes it. On a link the click stays on the page once the chat is starting or on. Until then the link goes to its address as usual: for a visitor the chat is off for (the site is Off, or in Test and this is not a test tab), and for one who has not allowed support yet (see Consent – a button suits a banner-first flow better). A link to #chat opens the chat on arrival and when clicked; once the chat is on, #chat is taken out of the address so the same link works again. ?aicmo_chat=open on any address opens the chat when the page loads.

html
<button data-aicmo-chat>Chat with us</button>
<a href="/contact" data-aicmo-chat>Contact us</a>
<a href="#chat">Talk to us</a>

From your code, through the queue – safe before the script has loaded:

js
AICMO.push(['chat', 'open']);

To call AICMO.chat(…) directly before the script has loaded, add this line once, next to init:

js
AICMO.chat = AICMO.chat || function () { AICMO.push(['chat'].concat([].slice.call(arguments))); };
CallWhat it does
AICMO.chat('open'), ('close'), ('toggle')Opens, closes or toggles the panel.
AICMO.chat('open', 'history'), ('open', 'inbox')Opens the panel on History or on the Inbox tab, where the visitor has that tab. ('open', 'updates') still works as well.
AICMO.chat('show'), ('hide')Shows or hides the launcher on this page – hide it on a checkout, say. Your own buttons still open the chat.
AICMO.chat('prefill', text)Puts words in the message box without sending them.
AICMO.chat('unread')Returns how many of your team's replies the visitor has not seen in the chat (the Inbox tab's messages are not counted): the count stored in the browser until the chat's script loads, then the live one.
AICMO.chat('on', event, fn), ('off', event, fn)Listens to an event, or stops.
AICMO.chat('identify', { email or external_id, hash, chat_sig?, exp?, name? })Tells the chat who a signed-in customer is, for this page; with chat_sig and exp, their chat follows them to other devices – see Their chat on every device.
AICMO.chat('reset')Forgets this browser's conversation; the next message starts a new one.
AICMO.reset()Sign-out: forgets the stored identity, its signature, the last identity-cookie value and the chat; when someone had been identified, also a fresh visitor ID, a new anonymous analytics person and their inbox messages cleared.

The events:

EventWhen
readyThe chat has loaded and is ready.
open, closeThe panel opened or closed.
unreadThe unread count changed – your function gets the number.
messageA message appeared – your function gets { from: 'you' or 'team', body }.
blockedSomething asked for the chat before the visitor allowed support.

With Site chat on, AICMO.openInbox() and AICMO.toggleInbox() open the panel on its Inbox tab.

From Journeys, a website message's button can Open the chat instead of going to a link – a proactive "Need help choosing?" with your journey's targeting and holdout. It needs Site chat in Test or Live on that site.

Page rules (Where it shows) match the path of the address. In a single-page app the launcher follows navigation; routes that live after a # in the address are not told apart.

Content Security Policy

On a site with a Content Security Policy, the chat needs nothing beyond the SDK itself: it loads its own file, a small language file when it speaks something other than English, and – when visitors can attach files – a small file for them, all from the same address as tracker.js, and talks to the same address. If you show your logo in the header, your policy must allow images from your logo's address.

With files on, allow three more things. A visitor's files go to an upload address of their own, https://uploads.theaicmo.com, which your policy must allow in connect-src. Photos in the conversation are drawn through short-lived links from the address your files are served from, which it must allow in img-src. And a photo waiting in the message box, before it is sent, is drawn from the visitor's own browser, which needs blob: in img-src. The Site chat row's Show section on the Website SDK page has the lines to add as soon as files are on in the draft or the published chat:

text
# Content-Security-Policy – with files on
connect-src 'self' https://theaicmo.com https://uploads.theaicmo.com
img-src 'self' blob: <the address your files are served from>

Where the Show section cannot name an address, your browser's console does: it names each address your policy blocks. A policy whose img-src does not allow them shows each photo by its name instead of as a picture – it still opens in a new tab with a tap – and a photo in the message box as a small file icon.

If your policy uses a nonce, the chat carries it: it reads the nonce from the tracker.js script tag, or takes it from init:

js
AICMO.push(['init', { siteId: 'lpo_your_site_id', nonce: 'your-page-nonce' }]);

If the policy blocks the chat's styles, nothing appears on the page – never an unstyled chat – and the browser's console says [AICMO] Site chat: blocked by Content-Security-Policy – pass init({ nonce }).

What it keeps in the browser

The chat sets no cookies. What it keeps is under the Support category, and is deleted when support stops being allowed. <site> is your site key.

NameWhereWhat it holds
aicmo_chat_<site>localStorageA random key for this browser's conversation, the conversation, a short fingerprint of the signed-in identity, when the newest message arrived, whether it was resolved, and the unread count. Deleted 30 days after the last message, when a new conversation starts after a resolved one, and by AICMO.reset() or AICMO.chat('reset').
aicmo_chat_ui_<site>sessionStorageThis tab's panel: open or closed and on which tab, an unsent draft, messages not yet confirmed as sent (with the names of the files attached to them), previews already dismissed, the reply address given in this tab, the unread count. Ends with the tab; also deleted by AICMO.reset() or AICMO.chat('reset').
aicmo_chat_test_<site>sessionStorageThis tab opened the test link. Ends with the tab, or with ?aicmo_chat=off.

A draft survives a page change, and a message sent just as the visitor navigated is sent again on the next page, with its files – never twice. Files picked but not yet sent are not carried to the next page. History is never kept in the browser: the list is read when the panel opens and lives only in the page. What AICMO.reset() deletes beyond the chat – the identity entries and, when someone had been identified, the visitor ID – is listed with the rest of the SDK's storage in Website SDK.

Where conversations land

Every conversation lands in Helpdesk (Customers → Helpdesk) under the Chat channel, tagged test when it came from a test tab. The desk shows whether the visitor is still on the site and on which page, their device, language and local time, when they have seen your reply, and where your reply will go if they have left. See Helpdesk.

Your AI setting applies to chats as to every other conversation – Observe, Draft or Auto – and the AI answers in the chat's register: one to three short sentences, no greeting or sign-off. A chat message that touches a regulated topic, such as a request to self-exclude, pauses the AI on that conversation until a person on the desk allows it again.

Limits that protect your credits

The chat is public – anyone who loads your site can write – so it has limits of its own. None of them can switch your chat off:

  • A browser or network that sends many messages in a short time is asked to wait: You’re sending messages quickly – try again in a moment. One network can start 10 new conversations in 10 minutes and 100 a day – room for the many visitors a mobile carrier puts behind one shared address.
  • Files have limits of their own – see Files visitors attach.
  • A sudden flood of new conversations on one site still arrives on the desk, tagged flood, and waits for a person without the AI.
  • AI turns a day (Optional) caps how often the AI answers on the site; one conversation also has a daily cap of its own. Past either, messages wait for a person, and the desk says why on the conversation.