Conversations SDK: expose the current unread conversation count on page load

As a developer embedding the HubSpot chat widget in a web app, I want to read the current unread conversation count when the page loads, so that I can show an unread indicator in my own UI without waiting for the count to change.

Problem

unreadConversationCountChanged only fires when the count changes. It does not deliver the current value on page load. There is no synchronous getter either — window.HubSpotConversations.widget.status() returns only { loaded }.

As a result, the two most common cases for an unread badge do not work:

  1. The visitor reloads the page (or navigates) while a reply is unread.
  2. An agent replies while the visitor has the app closed, and the visitor returns later.

In both cases our UI shows “no unread” until the agent happens to send another message.

Requested change

Either of the following would solve it:

  • Add a getter, e.g. window.HubSpotConversations.widget.unreadConversationCount(), or include unreadCount in widget.status().
  • Or fire unreadConversationCountChanged once with the current value after the widget is initialized (hsConversationsOnReady).

The first is preferable because it is explicit and does not change existing event semantics.

Why this matters / what we had to do instead

We tried to reconstruct the unread state server-side and found there is no supported way:

  • Conversations API v3 threads and messages expose no read/unread field, and there is no endpoint to mark a thread as read.
  • Contact properties have no equivalent.

We ended up approximating it by storing “last time the visitor opened the chat” ourselves and comparing it against latestMessageSentTimestamp on threads fetched via GET /conversations/v3/conversations/threads?associatedContactId=....

That approximation is expensive and fragile:

  • It requires conversations.read + crm.objects.contacts.read scopes just to render a badge.
  • latestMessageSentTimestamp includes chatflow bot replies, so it cannot distinguish a human agent reply from an automated one at the thread level.
  • The endpoint returns every channel in the inbox, so email threads have to be filtered out by originalChannelId or the badge fires for people who only ever emailed support.
  • Threads carry the pre-merge associatedContactId after contacts are merged, while an email lookup returns the surviving contact ID, so merged contacts silently return nothing unless hs_merged_object_ids is also queried.
  • sort=latestMessageTimestamp is ascending only (a - prefix silently returns zero results), so the newest threads are the ones truncated first and pagination is mandatory.

A single client-side getter would replace all of this.

Related discussions