# Message history

> The per-app audit trail of published messages: what the engine records, how long it keeps it, how payloads are trimmed, which publish path each message came from, and who is allowed to read it.

Message history is the audit trail for everything published on a
ClutchCall Realtime app. Where the Stats screen counts *how much*
traffic an app carried, message history answers *what* was published and
*by whom*: one row per message the engine broadcast, with its channel,
event name, sender, timestamp, and body.

The engine records at the single point every publish path converges on,
so a trigger from your server SDK, a publish from the console's Event
Creator, and a client event sent straight from a browser all land in the
same list.

## What gets recorded

Each broadcast message produces one row scoped to the org and the app it
was published on. The fields the console reads are:

| Field           | Meaning                                                        |
| --------------- | -------------------------------------------------------------- |
| `ts_ms`         | When the engine broadcast the message, in epoch milliseconds.  |
| `channel`       | The channel the message was published to.                      |
| `event`         | The event name.                                                |
| `origin`        | The publish path. See [Sender identity by publish path](#sender-identity-by-publish-path). |
| `payload`       | The message body as recorded — possibly trimmed.               |
| `payload_bytes` | The size of the message as published, before any trimming.     |
| `truncated`     | `1` when the recorded `payload` is shorter than the published message. |
| `actor_id`      | The credential or console account that published, when known.  |
| `actor_label`   | A display name for a console publisher, when known.            |
| `user_id`       | The authenticated end user behind a browser publish, when known. |
| `socket_id`     | The connection that published, for browser-originated messages. |
| `peer_addr`     | The network peer the publish arrived from.                     |

Rows appear within seconds of a publish. Newly created apps show an
empty list until the app publishes for the first time.

## Retention and payload trimming

Message history is kept for **90 days**. Rows older than that are no
longer readable, including via the longest selectable time window.

Payloads are trimmed by the engine at record time: long messages are
stored shortened, and the row carries `truncated = 1` to mark that the
stored body is not the whole message. `payload_bytes` always reports the
size of the message as it was actually published, so a trimmed row still
tells you how large the original was. The console surfaces this on an
expanded row as a note next to the body.

Two consequences worth planning around:

- **Message history is not a message store.** Do not treat a trimmed
  payload as a durable copy of application data. Keep your own record of
  anything you need to replay or reconcile.
- **Search sees what was recorded.** A body search matches the stored
  payload, so text that fell past the trim point is not searchable.

## Sender identity by publish path

Every row carries an `origin` describing which path the publish came in
on. The console presents four labels:

| Label          | `origin`               | What published                                            |
| -------------- | ---------------------- | --------------------------------------------------------- |
| **Server SDK** | `rest`                 | Your backend, using an app key.                           |
| **Console**    | `console`              | A publish from the console, such as the Event Creator.    |
| **Browser**    | `client`               | A client event sent directly from a connected browser.    |
| **Platform**   | `internal`, `platform` | The platform itself, rather than something you triggered. |

The first three are the paths you drive. `Platform` is the neutral case
and covers messages the engine originated on its own behalf.

The sender shown for a row is resolved in the terms of whoever sent it —
a person where there is one, otherwise the credential or connection that
acted:

- **Server SDK** — the publishing key (`actor_id`), with `peer_addr` as
  the secondary detail. Rows with no key attributed read as
  `Server SDK`.
- **Console** — the console account's display name (`actor_label`),
  falling back to `actor_id`, then to `Console user`.
- **Browser** — the authenticated `user_id` with the `socket_id`
  underneath. Unauthenticated connections show the `socket_id` alone, or
  `Anonymous client` when there is none.
- **Platform** — shown as `Platform`, with no identity attached.

An expanded row also lists the `user_id`, `socket_id`, and `peer_addr`
individually where they are present, which is what you need when
correlating a single message back to one connection.

## Who can read message history

Reading message history is restricted to **organization admins and
owners**. The procedure enforces this server-side: a member without
admin rights gets a `FORBIDDEN` error, and the console renders that as an
explanatory state rather than a failure.

The restriction exists because these rows reproduce two things the
aggregate counters do not: the contents your app publishes, and the
identities of the end users who published them. Org-level aggregate
metrics stay available to members; per-message bodies and identities do
not.

If you need access, ask an owner or admin of the organization. Access is
also scoped to the organization the app belongs to — you must be signed
in and have an org selected before any rows are fetched.

## Time window and filters

A query selects a trailing time window, expressed to the procedure as
`windowMinutes`:

| Window | `windowMinutes` |
| ------ | --------------- |
| 1h     | `60`            |
| 24h    | `1440`          |
| 7d     | `10080`         |
| 30d    | `43200`         |

Rows can additionally be narrowed by publish path via `origins`, an array
of `origin` values. The console exposes `rest`, `console`, and `client`
as filters plus an unfiltered "All"; omitting `origins` returns every
path, including platform-originated rows.

A query returns the newest rows first, up to the requested `limit`. The
console requests 200 and says so when the list is full, so a busy app in
a wide window shows the most recent 200 messages rather than everything
in the range. Narrow the window or add a filter to see further back.

## Search

The `search` input matches across channel, event name, sender, and the
recorded message body. The console debounces typing before issuing the
query, so the analytics store sees one request per pause rather than one
per keystroke.

Search composes with the origin filter and the time window — all three
narrow the same query. Because search runs against the stored payload,
see the caveat in
[Retention and payload trimming](#retention-and-payload-trimming) about
trimmed bodies.

## Related

- [Authentication](/concepts/authentication) — API keys and the org scoping that message history inherits
- [Telemetry](/platform/telemetry) — the aggregate metric streams, for "how much" rather than "what, by whom"
