a question answered, a sale keptthe chat needs no cookie bannerunlimited teammatesreply from slack or our phone app

/blog / tests

which chat widgets set cookies before anyone clicks

· by kuba · 10 min read

A cookie crossed out with a lime bar on a pink background, the startled Spookat cat holding up a sign that reads 0.

A visitor lands on your page, reads for ten seconds and leaves without touching the chat. We measured what seven chat widgets leave behind in that case: cookies, localStorage, sessionStorage, IndexedDB, and the servers they talk to. Spookat leaves nothing and talks to one server, our CDN. Every rival that booted wrote something to the visitor’s browser, and some of it is kept for more than a year.

the method, in one paragraph

Same lab as the rest of this series (how we test). Chrome 153, a brand-new profile for every run, 7 runs per widget, measured on 2026-09-27. The page loads, and for 25 seconds nothing is clicked, scrolled or typed. Then we read every cookie in the browser and every localStorage, sessionStorage and IndexedDB entry in every frame. We record names, domains, lifetimes, flags and sizes, never values.

Four widgets refuse to run on a domain their owner didn’t allow (Crisp, Intercom, LiveChat and Chatwoot), and the ids we could use belong to the vendors’ own sites. On our test page, before refusing, none of the four wrote anything. So for those four we loaded the vendor’s own site, where the widget runs, and loaded it again with the widget blocked. A cookie or key counts as the widget’s only if it appears in at least half the runs with the widget and in none without it. That separates the widget from the vendor’s own analytics and ad cookies, which their marketing sites set plenty of.

We didn’t touch any consent banner on those sites: not accept, not reject, not close. Some widgets may behave differently once a visitor has made a choice, and that’s outside this test.

the results

widget cookies before a click longest kept local and session storage servers contacted
Spookat 0 none none 1 (cdn.spookat.com)
Tawk.to 3 on the site’s domain 180 days 2 localStorage keys, 1 sessionStorage key 9 hosts on tawk.to
Tidio 0 none 1 localStorage key (3 kb) 2 hosts on tidio.co
Crisp 1 on the site’s domain 182 days 4 localStorage keys 3 hosts on crisp.chat
Intercom 3 on the site’s domain 270 days 1 localStorage key, 1 sessionStorage key 4 hosts on intercom.io and intercomcdn.com
LiveChat 2 on accounts.livechatinc.com (+1 that expires at once) 400 days 4 localStorage keys, 1 sessionStorage key, in its iframe 4 hosts on livechatinc.com
Chatwoot 1 on the site’s domain, 1 on app.chatwoot.com 365 days 6 localStorage keys (460 kb) in its iframe 3 hosts on chatwoot.com, CloudFront and S3

Nobody used IndexedDB. Every result was the same in all 7 runs of each widget.

“On the site’s domain” means the widget’s own script wrote the cookie with JavaScript, on whatever domain the page is on. On your site, those would be your cookies, set by someone else’s code.

widget by widget

spookat: nothing

The launcher is one script from cdn.spookat.com. It draws a button and stops. It has no code that reads or writes cookies, and no storage call runs before the click.

After a click, still nothing is stored. The chat panel loads, connects to api.spookat.com, and shows the greeting. Only when the visitor sends their first message does the panel write one localStorage key, so the conversation survives a page reload. That’s the first time Spookat puts anything in the browser: after a person asked for a chat and wrote in it. The widget never sets a cookie, then or later.

tawk.to: three cookies and a token

Tawk.to boots anywhere, so these are its full numbers on a normal page. Before any click it sets three cookies on the page’s own domain:

  • twk_uuid_<property>, kept 180 days
  • TawkConnectionTime, a session cookie
  • twk_idm_key, a session cookie

It writes two localStorage keys, one of them a 464-byte token, and one sessionStorage key with the previous page. It opens a websocket to one of several numbered tawk.to servers. Across our 7 runs it used 7 different ones, which is why the table shows 9 hosts.

tidio: one key, no cookies

Tidio set no cookies. It wrote one localStorage key with its state (about 3 kb) and opened a websocket to socket.tidio.co. Its own widget is set to stay hidden on tidio.com, so our page called Tidio’s documented tidioChatApi.show() before any click to show the launcher. That call is the only change to a stock install.

crisp: a session for six months

On crisp.chat, before any click, the widget sets one cookie on the site’s domain, crisp-client/session/<website id>, kept 182 days. It also writes four localStorage keys: the same session, an expiry, and a visit count and last-visit time for its triggers. Its websocket goes to client.relay.crisp.chat.

intercom: an id kept for nine months

On intercom.com the widget sets three cookies on the site’s domain:

  • intercom-id-<app>, kept 270 days
  • intercom-device-id-<app>, kept 270 days
  • intercom-session-<app>, kept 7 days

Plus its state in localStorage (2.4 kb) and a list of played notifications in sessionStorage, which fits the message intercom.com pushes to new visitors. A quieter install may not write that one.

livechat: third-party cookies, partitioned

LiveChat’s cookies sit on the vendor’s domain, not the site’s, and its server sets them: __lc_cid and __lc_cst on accounts.livechatinc.com, kept 400 days, HttpOnly, Secure, SameSite=None, and partitioned. Partitioned means Chrome keeps a separate copy per site the visitor is on, so the same cookie can’t follow them from one site to another. A third, __oauth_redirect_detector, expires as soon as it’s set.

Its iframe from secure.livechatinc.com also writes four localStorage keys, among them an auth token and a “has visited” flag, and one sessionStorage key.

chatwoot: 460 kb in localStorage

On chatwoot.com’s help center (its homepage didn’t load the widget in our tries), the widget set a cw_conversation cookie on the site’s domain, kept 365 days, and a session cookie on app.chatwoot.com. Its iframe wrote six localStorage keys. One of them holds the six most-read help articles, and it’s 460 kb, which is a lot of someone else’s content cached on your visitor’s device just in case.

Two different things hide behind “the widget sets a cookie”.

Cookies on your domain. Tawk.to, Crisp, Intercom and Chatwoot write theirs with JavaScript, on the domain of the page they’re running on. To the browser these are first-party cookies: yours. They survive third-party cookie blocking, because they aren’t third-party, and they’re sent with every request to your own server too. A visitor’s cookie list on your site will show twk_uuid_… or intercom-id-… next to your own session cookie.

Cookies on the vendor’s domain. LiveChat’s live on accounts.livechatinc.com, and Chatwoot’s session cookie on app.chatwoot.com. These are third-party on your site. Chrome still allows third-party cookies by default, but LiveChat’s are partitioned, so each site gets its own jar and they can’t link a visitor across sites.

Browsers don’t treat all of them the same. Safari’s Intelligent Tracking Prevention caps cookies written by JavaScript at 7 days, so on an iPhone Tawk.to’s 180-day and Intercom’s 270-day cookies last a week at most. Chrome caps every cookie at 400 days, which is exactly what LiveChat asks for.

the four that refused still called home

On our test page, where Crisp, Intercom, LiveChat and Chatwoot weren’t allowed to run, none of them stored anything. They did contact their servers before giving up: Intercom three hosts (its loader, its CDN and its API, which answered 403), LiveChat two, Crisp two, including a websocket it opened before showing “Invalid website”, and Chatwoot one. If you remove a domain from a vendor’s allowlist and leave the snippet on the page, your visitors still send those requests.

the servers your visitors talk to

Every request carries the visitor’s IP address, their browser’s user agent and, in most cases, the address of the page they’re on. That’s how the web works, and it means every server a widget contacts learns that this visitor was on this page at this moment, whether or not they ever chat.

Before any interaction, on a page where it could boot:

  • Spookat: 1 host, cdn.spookat.com. It’s ours, and Cloudflare sits in front of it, as it does for all our hosts; Cloudflare is a listed subprocessor on our privacy page. We never store an IP.
  • Tawk.to: 9 hosts across our runs, one at a time for the websocket.
  • Tidio, Crisp: 2 and 3 hosts.
  • Intercom, LiveChat, Chatwoot: 3 to 4 hosts, including websockets that stay open for as long as the tab does.

A websocket opened before a click is a live connection to the vendor for the whole visit. It’s how a widget can show “we’re online” or push a message, and it’s also a steady signal that someone is on the page.

We’re not lawyers, and this isn’t legal advice. Here’s the part that’s plain fact.

The EU rule that consent banners come from, Article 5(3) of the ePrivacy Directive, isn’t about cookies specifically. It covers storing information on a visitor’s device, or reading information already stored there. localStorage and sessionStorage are storage on the device just as much as cookies are. The rule’s exception is for storage that’s strictly necessary to provide a service the visitor explicitly asked for.

A visitor who hasn’t touched the chat hasn’t asked for it. So when a widget writes an id to their browser on page load, you have to decide whether that’s covered by the exception, which is a hard argument to make for a visitor who never chats, or whether the widget belongs behind your consent banner. Most of the widgets in this test put you in front of that question. A widget that stores nothing before a click doesn’t.

Putting the widget behind the banner has a cost of its own. Consent tools can hold a script until the visitor accepts, and most will. Then a visitor who rejects, or simply ignores the banner, gets no chat at all, often the same visitor who had a question before buying. You end up choosing between a banner that covers the chat and a chat that only works for people who clicked “accept all”.

The other way out is a widget that doesn’t store anything until the visitor asks for it. Then the storage question doesn’t come up on page load at all, and it comes up later only for the people who opened the chat and wrote in it, who are exactly the people who asked for the service.

What to ask your vendor, and your lawyer:

  • What does the widget write before any interaction, and for how long?
  • Can it wait until the visitor clicks, and does waiting break anything?
  • Which of its servers get a request before a click, and in which country are they?
  • Is there a setting that stops storage before interaction, and what does it turn off?

what we’d want from any widget

  • Nothing written to the visitor’s browser before they interact with the chat.
  • No connection held open before a click. A button doesn’t need a websocket.
  • One server contacted, not a handful, and the vendor can tell you where it is.
  • After a visitor starts chatting, one key for the conversation, not an id meant to recognise them for the next year.
  • A cookie list you can write in one line of your privacy policy.

That’s the bar we built Spookat to, and the table above is what it looks like when a widget meets it.

how to check your own widget

  1. Open your site in a private window, so there’s nothing stored yet.
  2. Open DevTools, go to Application, and look at Cookies, Local storage, Session storage and IndexedDB for your domain and for every frame in the list.
  3. Don’t touch the page for 25 seconds, then reload the Application view. Anything new came from something on the page. Blocking the widget’s script in the Network tab and repeating tells you which of it is the widget’s.

Our raw data is at /research/cookies-2026-09.json: every run, every cookie with its domain, lifetime and flags, every storage key with its size, and for vendor sites the names that disappeared with the widget blocked. The scripts are in the widget lab. The byte side of the same runs is in the weigh-in.

why ours stores nothing

Not because a lawyer told us to. A launcher has one job before the click: be a button. Nothing about a button needs an id in the visitor’s browser, a connection to our server, or a way to recognise them tomorrow. So Spookat doesn’t do any of that until someone writes in the chat, and after that it keeps exactly one key, the conversation, so the chat survives a reload. No cookies, before or after.

Add a chat that leaves no trace until someone uses it: 14 days, no card.

liked it? get on the list.

invites go out in small batches. when yours lands: 14 days free, no card.

we only email you about your invite and the launch. you can stop them anytime. privacy