> ## Documentation Index
> Fetch the complete documentation index at: https://docs.utrack.club/llms.txt
> Use this file to discover all available pages before exploring further.

# WS Data Types & Events

> All the data types and events that the websocket sends

## Base Message Format

Every message sent over the websocket follows this base format:

```tsx theme={null}
export interface WebsocketWorkerEvent {
    id: string; // Unique ID for this event (use for debouncing and deduplication across multiple connections)
    type: string; // The type of event being sent
}
```

***

## Tweet Events

<Accordion description="The fastest event for any new tweet. Sent immediately with compressed data." icon="bolt" title="tweet.mini.update">
  ```tsx theme={null}
  export interface MiniTweetUpdate extends WebsocketWorkerEvent {
      type: 'tweet.mini.update';
      tweet: TwitterMiniTweet;
  }
  ```

  This is always the **first** event fired for any new tweet. It prioritizes speed over completeness, you get core tweet info instantly, but subtweet data (quotes, retweets, replies) won't be populated yet.

  A `tweet.update` will follow shortly after if the tweet:

  * Is quoting, replying to, or retweeting another tweet
</Accordion>

<Accordion description="Full tweet data including subtweets (up to 2 levels deep)." icon="box-heart" title="tweet.update">
  ```tsx theme={null}
  export interface TweetUpdate extends WebsocketWorkerEvent {
      type: 'tweet.update';
      tweet: TwitterTweet;
  }
  ```

  Sent a bit after `tweet.mini.update` with complete tweet data including resolved subtweets.

  **Note:** Subtweet chains are resolved up to **2 levels deep**. If a chain is 3+ levels (e.g. a retweet of a reply to another tweet), levels 3+ will have minimal/default data. See `tweet.full` for fully resolved deep chains.
</Accordion>

<Accordion description="Expanded tweet data with full text and article content." icon="sparkles" title="tweet.update.expanded">
  ```tsx theme={null}
  export interface TweetUpdateExpanded extends WebsocketWorkerEvent {
      type: 'tweet.update.expanded';
      tweet: TwitterTweet;
  }
  ```

  Same as `tweet.update` but with **full untruncated text** and article content if present.

  Only sent when:

  * The tweet text was truncated in `tweet.update`
  * The tweet contains an article
</Accordion>

<Accordion description="Fully resolved deep chain (3-5+ levels) with complete data at every level." icon="link" title="tweet.full">
  ```tsx theme={null}
  export interface TweetFull extends WebsocketWorkerEvent {
      type: 'tweet.full';
      tweet: TwitterTweet;
  }
  ```

  Sent when a tweet has a subtweet chain **3 or more levels deep**. Every level in the chain is fully resolved with complete author data, metrics, media, and body text.

  **Only triggered for chains 3+ levels deep.** Standalone tweets, simple retweets, and single-level quotes/replies will not trigger this event.

  **Timeline:**

  * `tweet.mini.update` arrives first (fastest)
  * `tweet.update` arrives \~60ms later (levels 3+ may have empty data)
  * `tweet.full` arrives \~200-500ms later (all levels fully resolved)

  **Example chain (5 levels):** 

  ```
  Level 1: @elonmusk (QUOTE) "True"
    └─ Level 2: @DavidSacks (QUOTE) "And guess where those staffers went..."
        └─ Level 3: @beffjezos (QUOTE) "Here's the receipt..."
            └─ Level 4: @mattyglesias (QUOTE) "The hypothetical hypocrisy..."
                └─ Level 5: @beffjezos (TWEET) "Let's be real if the Dems had won..."
  ```

  In `tweet.update`, levels 3-5 would have empty/default data. In `tweet.full`, every level has complete data.

  Supports up to 6 levels including the main tweet, we might increase it in the future, but 6 levels seems good for most use cases for now
</Accordion>

***

## Profile Events

<Accordion description="Fired when a tracked user pins a tweet to their profile." icon="thumbtack" title="profile.pinned.update">
  ```tsx theme={null}
  export interface WorkerProfilePinnedUpdateEvent extends WebsocketWorkerEvent {
      type: 'profile.pinned.update';
      user: TwitterUser;
      pinned: TwitterTweet[];
  }
  ```
</Accordion>

<Accordion description="Fired when a tracked user unpins a tweet from their profile." icon="thumbtack" title="profile.unpinned.update">
  ```tsx theme={null}
  export interface WorkerProfileUnpinnedUpdateEvent extends WebsocketWorkerEvent {
      type: 'profile.unpinned.update';
      user: TwitterUser;
      pinned: TwitterTweet[];
  }
  ```
</Accordion>

<Accordion description="Fired when a tracked user updates their profile information." icon="user-pen" title="profile.update">
  ```tsx theme={null}
  export interface ProfileUpdate extends WebsocketWorkerEvent {
      type: 'profile.update';
      user: TwitterUser;   // The new profile information
      before: TwitterUser; // The old profile information
  }
  ```
</Accordion>

***

## Activity Events

<Accordion description="Fired when a tracked user follows or unfollows another account." icon="user-plus" title="following.update">
  ```tsx theme={null}
  export interface FollowingUpdate extends WebsocketWorkerEvent {
      type: 'following.update';
      change: 'unfollowed' | 'followed';
      following: TwitterUser; // The target user who was followed/unfollowed
      user: TwitterUser;      // The tracked user who performed the action
  }
  ```
</Accordion>

<Accordion description="Fired when a tracked user's tweet is deleted." icon="trash" title="tweet.deleted">
  ```tsx theme={null}
  export interface DeletedTweet extends WebsocketWorkerEvent {
      type: 'tweet.deleted';
      tweet: TwitterTweet; // Metrics may be outdated (likes, replies, retweets, etc)
      deleted_at: number;  // UNIX timestamp in milliseconds
  }
  ```

  <Warning>
    Don't rely on metrics being accurate for deleted tweets — they reflect the last known values before deletion.
  </Warning>
</Accordion>

***

## Types

### TwitterUser

<Accordion description="Complete user profile data." icon="user" title="TwitterUser">
  ```tsx theme={null}
  export interface TwitterUser {
      id: string;
      handle: string;
      private: boolean;
      verified: {
          type: 'none' | 'blue' | 'gold' | 'gray';
          label: null | {
              description: string;
              badge: null | string;
              url: null | string;
          };
      };
      sensitive: boolean;
      restricted: boolean;
      joined_at: number;

      profile: {
          name: string;
          location: null | string;
          avatar: null | string;
          banner: null | string;
          pinned: string[];
          url: null | {
              name: string;
              url: string;
              tco: string;
          };
          description: {
              text: string;
              urls: {
                  name: string;
                  url: string;
                  tco: string;
              }[];
          };
      };

      metrics: {
          likes: number;
          media: number;
          tweets: number;
          friends: number;
          followers: number;
          following: number;
      };
  }
  ```

  | Field             | Description                                                                                                  |
  | ----------------- | ------------------------------------------------------------------------------------------------------------ |
  | `id`              | The user's account ID                                                                                        |
  | `handle`          | The user's handle (without @)                                                                                |
  | `private`         | Whether the account is currently private                                                                     |
  | `verified.type`   | `blue` = blue checkmark, `gold` = business/organization, `gray` = government/official, `none` = not verified |
  | `verified.label`  | Affiliated organization info (badge, description, profile link) — null if none                               |
  | `sensitive`       | Whether the account is flagged for sensitive content                                                         |
  | `restricted`      | Whether the account is restricted for unusual activity                                                       |
  | `joined_at`       | UNIX timestamp in milliseconds                                                                               |
  | `profile.pinned`  | Array of pinned tweet IDs                                                                                    |
  | `metrics.friends` | Mutual followers (people who follow the user and the user follows back)                                      |
</Accordion>

### TwitterMiniUser

<Accordion description="Compressed user data for fast delivery." icon="user" title="TwitterMiniUser">
  ```tsx theme={null}
  export interface TwitterMiniUser {
      id: string;
      handle: string;
      verified: {
          type: 'none' | 'blue' | 'gold' | 'gray';
          label: null | {
              description: string;
              badge: null | string;
              url: null | string;
          };
      };
      profile: {
          name: string;
          avatar: null | string;
      };
      metrics: {
          following: number;
          followers: number;
      };
  }
  ```
</Accordion>

### TwitterTweet

<Accordion description="Complete tweet data with all fields." icon="message" title="TwitterTweet">
  ```tsx theme={null}
  export interface TwitterTweet {
      id: string;
      type: 'TWEET' | 'RETWEET' | 'QUOTE' | 'REPLY';
      created_at: number;
      author: TwitterUser;
      subtweet: null | TwitterTweet;

      reply: null | {
          id: string;
          handle: string;
      };

      quoted: null | {
          id: string;
          handle: string;
      };

      body: {
          text: string;
          urls: {
              name: string;
              url: string;
              tco: string;
          }[];
          mentions: {
              id: string;
              name: string;
              handle: string;
          }[];
          components: (
              | { type: 'text'; text: string; bold: boolean; italics: boolean; }
              | { type: 'image'; url: string; }
              | { type: 'video'; url: string; }
          )[];
      };

      media: {
          images: string[];
          videos: string[];
          thumbnails: string[];
          proxied: null | {
              images: string[];
              thumbnails: string[];
          };
      };

      grok: null | {
          id: string;
          conversation: {
              from: 'USER' | 'AGENT';
              message: string;
              images: string[];
          }[];
      };

      card: null | {
          url: string;
          image: string;
          title: string;
          description: string;
      };

      poll: null | {
          ends_at: number;
          updated_at: number;
          choices: {
              label: string;
              count: number;
          }[];
      };

      article: null | {
          id: string;
          title: string;
          thumbnail: null | string;
          created_at: number;
          updated_at: number;
          body: {
              text: string;
              components: (
                  | { type: 'divider'; }
                  | {
                      type: 'text';
                      variant:
                          | 'header-one'
                          | 'header-two'
                          | 'paragraph'
                          | 'blockquote'
                          | 'ordered-list'
                          | 'unordered-list'
                          | 'latex-box'
                          | 'markdown-box';
                      lines: {
                          text: string;
                          styles: {
                              from: number;
                              to: number;
                              text: 'bold' | 'italics' | 'strikethrough';
                          }[];
                          urls: {
                              from: number;
                              to: number;
                              url: string;
                          }[];
                      }[];
                  }
                  | {
                      type: 'media';
                      variant: 'image' | 'gif' | 'video';
                      url: string;
                      thumbnail: string;
                      caption: null | string;
                  }
                  | {
                      type: 'tweet';
                      tweet: {
                          id: string;
                          url: string;
                          object: null | TwitterTweet;
                      };
                  }
              )[];
          };
      };

      community: null | object;

      metrics: {
          likes: number;
          quotes: number;
          replies: number;
          retweets: number;
          advanced: null | {
              views: number;
          };
      };
  }
  ```

  | Field              | Description                                                                                                                        |
  | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
  | `subtweet`         | The nested tweet this tweet references (reply parent, quoted tweet, or retweeted tweet). Recursive — can contain its own subtweet. |
  | `reply`            | If this is a REPLY, contains the ID and handle of the tweet being replied to                                                       |
  | `quoted`           | If this is a QUOTE, contains the ID and handle of the tweet being quoted                                                           |
  | `body.components`  | Rich text components for expanded/long tweets — only populated in `tweet.update.expanded`                                          |
  | `media.proxied`    | Proxied media URLs — not always available, fall back to normal URLs                                                                |
  | `grok`             | Embedded Grok AI conversation preview, if the tweet contains one                                                                   |
  | `card`             | URL card preview (image, title, description) for linked articles                                                                   |
  | `article`          | Full article content if the tweet is a Twitter article                                                                             |
  | `metrics.advanced` | View count — null until expanded version is fetched                                                                                |
</Accordion>

### TwitterMiniTweet

<Accordion description="Compressed tweet data for fast delivery." icon="message" title="TwitterMiniTweet">
  ```tsx theme={null}
  export interface TwitterMiniTweet {
      id: string;
      type: 'TWEET' | 'RETWEET' | 'QUOTE' | 'REPLY';
      created_at: number;
      author: TwitterMiniUser;
      subtweet: null | TwitterMiniTweet;

      reply: null | {
          id: string;
          handle: string;
      };

      quoted: null | {
          id: string;
          handle: string;
      };

      body: {
          text: string;
          urls: {
              name: string;
              url: string;
              tco: string;
          }[];
          mentions: {
              id: string;
              name: string;
              handle: string;
          }[];
      };

      media: {
          images: string[];
          videos: string[];
          thumbnails: string[];
          proxied: null | {
              images: string[];
          };
      };
  }
  ```
</Accordion>

***

## Event Flow

Here's the order events fire for different tweet types:

```
Standalone tweet:
  1. tweet.mini.update  (instant)
  2. tweet.update        (a tiny bit later, only if has URL/card)

Quote / Reply / Retweet (2 levels):
  1. tweet.mini.update  (instant)
  2. tweet.update        (a tiny bit later, subtweet fully resolved)

Deep chain (3+ levels):
  1. tweet.mini.update  (instant)
  2. tweet.update        (a tiny bit later, levels 3+ have minimal data)
  3. tweet.full          (a little more later, ALL levels fully resolved)
```

<Info>
  We are always working on new events — keep an eye on this section for updates!
</Info>
