# Realtime updates

Messaging tabs subscribe to Supabase Realtime channels. When your backend writes a new message, you POST to one Oaura endpoint and every connected widget refetches without the visitor doing anything. This is how unread badges go live and how the open thread updates while the visitor is reading it.

Realtime is only wired for messaging tabs. Default tabs don't subscribe to anything.

## How it works

Two channels exist per messaging tab:

| Channel                                                  | What it triggers                                             | Subscribed when                                                                                                            |
| -------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| User channel — `agent-<agentId>-user-<endUserId>-<hmac>` | Tab list refetches (badge updates, new conversation appears) | The widget loads any messaging tab and `get_items` returns a `listChannel` for the visitor. Persists across tab switches.  |
| Conversation channel — `conv-<conversationId>-<hmac>`    | Open `MessagingView` refetches the thread                    | The visitor opens a conversation and `item_clicked` returns a `conversationChannel`. Torn down when they close the thread. |

The `<hmac>` segment is an HMAC of the channel name keyed on the agent's `signing_secret`. The widget never sees the secret; it just receives the pre-computed channel name from `tab-fetch` / `tab-action`. This prevents a malicious page from guessing channel names and snooping on other agents' broadcasts.

## What you do to fire a refresh

POST to the `realtime-publish` edge function whenever your backend writes a new message. One call notifies both the recipient's tab list (`endUserId`) and any subscribers viewing the open thread (`conversationId`).

**Endpoint**

```
POST https://<your-supabase-ref>.supabase.co/functions/v1/realtime-publish
```

**Headers**

```
x-api-key: <agent api key>
Content-Type: application/json
```

**Body**

```json
{
  "endUserId":      "user-from-jwt-sub-claim",
  "conversationId": "conversation-uuid"
}
```

Both fields are optional individually, but you must provide at least one. The recommended pattern is to send both:

* `endUserId` — fires the user's list channel so the unread badge / preview / sort order updates regardless of which tab they're on.
* `conversationId` — fires the conversation channel so the open thread refetches if the recipient happens to be reading it right now.

**Response**

```json
{ "ok": true, "channels": ["agent-…-user-…-…", "conv-…-…"] }
```

The `channels` array is informational — you don't use the names for anything. A 200 + `ok: true` means the broadcast was queued.

## When to fire

Anywhere your backend writes a `messages_entry`-equivalent row in your store:

* New message from the operator to the visitor → POST with both ids.
* New message from another counterparty in the same thread → POST with both ids.
* Status change on the conversation (resolved, reopened) → POST with both ids.
* Bulk re-import / migration → don't bother; the next `get_items` covers it.

There is no rate limit beyond Supabase Realtime's own limits, but firing more than once per logical event is wasted work. Debounce on your side if your store emits multiple events per write.

## What if you don't call it?

The widget still works — but only updates when:

* The visitor reopens the tab (triggers a `get_items` call).
* The visitor refreshes the page.
* The visitor reopens the widget after closing it.

For a visitor who keeps the widget open, this means new messages can sit invisible indefinitely. For a UX that feels like a real chat, you want `realtime-publish`.

## Channel TTL and reconnects

Supabase Realtime handles reconnects automatically. If the visitor's WebSocket drops (laptop sleep, network change), the channel re-subscribes when it comes back. Any broadcasts that fired during the disconnect are lost, but the first `get_items` on reconnect catches up the list.

Channel names don't expire — they're a pure function of `agentId`, `endUserId` or `conversationId`, and the agent's `signing_secret`. If you rotate the signing secret, channel names change and existing subscriptions stop receiving broadcasts until the widget refetches (which it will, automatically, on its next `validate-widget`).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tillforty.com/oaura/tabs/realtime-updates.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
