Payloads, Token Detection & OCR

Complete reference for TweetStream alert data structures and enrichment features

Tweet Lifecycle

For tweet events, TweetStream sends a base content message first, optional update messages, lifecycle messages when observed, and a meta message when enrichment is ready:

  • content - The tweet text, author, media, and kind: post, reply, quote, or retweet (arrives first)
  • update - Optional matching kind/ref details, media, or URL expansion updates
  • meta - Enrichment data: OCR results, detected tokens, markets (arrives shortly after)
  • delete, pin, unpin - Deleted tweet and pinned tweet state changes when observed

Message Families

TweetStream uses one WebSocket envelope format for live events and a separate history response shape for replay:

  • tweet - tweet envelopes carry content, update, meta, delete, pin, and unpin events
  • account - account envelopes carry profile_update, follow, and unfollow events
  • control - control envelopes carry handle-management results such as twitter_handles_result
  • /api/history - the history API returns rows with tweetId, body, time, receivedTime, link, messageType, twitterHandle, twitterId, content, and optional meta

Tweet Content Type

type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
  badge: string | null;
  description: string;
  url: string | null;
};

type TweetAuthor = {
  banner?: string;
  bio?: string;
  followersCount?: number;
  followingCount?: number;
  id?: string;
  joinedAt?: number;
  location?: string;
  metrics?: {
    likes?: number;
    tweets?: number;
  };
  // Includes a leading @ when present, for example "@elonmusk".
  handle?: string;
  name?: string;
  platform?: 'twitter' | 'truth_social';
  profileImage?: string;
  url?: string;
  verifiedLabel?: TweetVerifiedLabel;
  verifiedType?: VerifiedType;
};

type Media = {
  url: string;
  type?: 'image' | 'video' | 'gif';
  thumbnail?: string;
};

type TweetUrl = {
  url: string;
  name?: string;
  tco?: string;
};

type TweetMention = {
  handle?: string;
  id?: string;
  name?: string;
};

type TweetContentKind = 'post' | 'reply' | 'quote' | 'retweet';

type TweetContent = {
  tweetId: string;
  kind: TweetContentKind;
  text: string;
  createdAt: number;
  author: TweetAuthor;
  link?: string;
  media?: Media[];
  mentions?: TweetMention[];
  receivedAt?: number;
  urls?: TweetUrl[];
  ref?: {
    type: 'reply' | 'quote' | 'retweet';
    tweetId?: string;
    text?: string;
    author?: TweetAuthor;
    media?: Media[];
  };
};

Use content.kind and content.ref.type to classify content as post, reply, quote, or retweet. The public ref type for reposts is retweet; TweetStream does not emit repost as a separate type. Truth Social posts use the same tweet content shape; check author.platform to distinguish platform.

  • Route classifiers by kind first, then ref.type when present. Do not classify retweets from top-level text alone.
  • For retweets, ref.text is the referenced tweet body. Top-level text can be empty or can duplicate the referenced body in live content frames, so treat it as display text, not proof of original authorship.
  • content arrives before update. Later update frames can add or complete matching kind/ref fields when relationship context becomes available; there is no max-delay guarantee.
  • The History API returns the latest merged content we observed, so replay can recover classification when a later live update supplied kind/ref. It cannot invent reference details that were never observed.

Example: Retweet Content Envelope

{
  "v": 1,
  "t": "tweet",
  "op": "content",
  "ts": 1702500000130,
  "d": {
    "tweetId": "2234567890",
    "kind": "retweet",
    "text": "",
    "createdAt": 1702500000000,
    "receivedAt": 1702500000123,
    "author": {
      "id": "111111",
      "handle": "@tracked_account",
      "name": "Tracked Account",
      "platform": "twitter"
    },
    "ref": {
      "type": "retweet",
      "tweetId": "1234567890",
      "text": "Original tweet body",
      "author": {
        "id": "222222",
        "handle": "@original_author",
        "name": "Original Author",
        "platform": "twitter"
      }
    }
  }
}

Tweet Lifecycle Event Types

Deletes, pins, and unpins use tweet envelopes. Pin events include the observed tweet content when available.

type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
  badge: string | null;
  description: string;
  url: string | null;
};

type TweetAuthor = {
  banner?: string;
  bio?: string;
  followersCount?: number;
  followingCount?: number;
  id?: string;
  joinedAt?: number;
  location?: string;
  metrics?: {
    likes?: number;
    tweets?: number;
  };
  // Includes a leading @ when present, for example "@elonmusk".
  handle?: string;
  name?: string;
  platform?: 'twitter' | 'truth_social';
  profileImage?: string;
  url?: string;
  verifiedLabel?: TweetVerifiedLabel;
  verifiedType?: VerifiedType;
};

type Media = {
  url: string;
  type?: 'image' | 'video' | 'gif';
  thumbnail?: string;
};

type TweetUrl = {
  url: string;
  name?: string;
  tco?: string;
};

type TweetMention = {
  handle?: string;
  id?: string;
  name?: string;
};

type TweetContentKind = 'post' | 'reply' | 'quote' | 'retweet';

type TweetContent = {
  tweetId: string;
  kind: TweetContentKind;
  text: string;
  createdAt: number;
  author: TweetAuthor;
  link?: string;
  media?: Media[];
  mentions?: TweetMention[];
  receivedAt?: number;
  urls?: TweetUrl[];
  ref?: {
    type: 'reply' | 'quote' | 'retweet';
    tweetId?: string;
    text?: string;
    author?: TweetAuthor;
    media?: Media[];
  };
};

type TweetDeleteEvent = {
  tweetId: string;
  eventId: string;
  deletedAt?: number;
  receivedAt?: number;
  author?: TweetAuthor;
  text?: string;
};

type TweetPinEvent = {
  tweetId: string;
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  action: 'pin' | 'unpin';
  author: TweetAuthor;
  text?: string;
  tweet?: TweetContent;
};

Account Event Types

Profile updates, follow notifications, and unfollow notifications use the account envelope family and share the richer actor fields shown below:

type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
  badge: string | null;
  description: string;
  url: string | null;
};

type TweetAuthor = {
  banner?: string;
  bio?: string;
  followersCount?: number;
  followingCount?: number;
  id?: string;
  joinedAt?: number;
  location?: string;
  metrics?: {
    likes?: number;
    tweets?: number;
  };
  // Includes a leading @ when present, for example "@elonmusk".
  handle?: string;
  name?: string;
  platform?: 'twitter' | 'truth_social';
  profileImage?: string;
  url?: string;
  verifiedLabel?: TweetVerifiedLabel;
  verifiedType?: VerifiedType;
};

type AccountEventActor = TweetAuthor & {
  websiteUrl?: string;
};

type ProfileUpdateEvent = {
  kind: 'PROFILE';
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  actor: AccountEventActor;
  changes: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
  previous?: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
};

type FollowEvent = {
  kind: 'FOLLOW' | 'UNFOLLOW';
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  actor: AccountEventActor;
  target: AccountEventActor;
};

Tweet Meta Type

The meta payload contains all enrichment data:

type TweetMeta = {
  tweetId: string;
  ocr?: {
    text: string;
  };
  detected?: {
    tokens?: Array<{
      symbol?: string;
      name?: string;
      contract?: string;
      chain?: string;
      networkId?: number;
      priceUsd?: number;
      sources: Array<'text' | 'ocr'>;
    }>;
    cex?: Array<{
      exchange: 'bybit' | 'binance' | 'hyperliquid';
      symbol?: string;
      priceUsd?: number;
      url?: string;
      baseAsset?: string;
      quoteAsset?: string;
      sources: Array<'text' | 'ocr'>;
    }>;
    prediction?: Array<{
      exchange: 'polymarket' | 'kalshi';
      marketId?: string;
      title?: string;
      priceUsd?: number;
      url?: string;
      sources: Array<'text' | 'ocr'>;
    }>;
  };
};

History Response Type

Historical replay returns tweet, profile, and follow rows in one response shape with metadata filters echoed back.

type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
  badge: string | null;
  description: string;
  url: string | null;
};

type TweetAuthor = {
  banner?: string;
  bio?: string;
  followersCount?: number;
  followingCount?: number;
  id?: string;
  joinedAt?: number;
  location?: string;
  metrics?: {
    likes?: number;
    tweets?: number;
  };
  // Includes a leading @ when present, for example "@elonmusk".
  handle?: string;
  name?: string;
  platform?: 'twitter' | 'truth_social';
  profileImage?: string;
  url?: string;
  verifiedLabel?: TweetVerifiedLabel;
  verifiedType?: VerifiedType;
};

type AccountEventActor = TweetAuthor & {
  websiteUrl?: string;
};

type ProfileUpdateEvent = {
  kind: 'PROFILE';
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  actor: AccountEventActor;
  changes: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
  previous?: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
};

type FollowEvent = {
  kind: 'FOLLOW' | 'UNFOLLOW';
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  actor: AccountEventActor;
  target: AccountEventActor;
};

type TweetMeta = {
  tweetId: string;
  ocr?: {
    text: string;
  };
  detected?: {
    tokens?: Array<{
      symbol?: string;
      name?: string;
      contract?: string;
      chain?: string;
      networkId?: number;
      priceUsd?: number;
      sources: Array<'text' | 'ocr'>;
    }>;
    cex?: Array<{
      exchange: 'bybit' | 'binance' | 'hyperliquid';
      symbol?: string;
      priceUsd?: number;
      url?: string;
      baseAsset?: string;
      quoteAsset?: string;
      sources: Array<'text' | 'ocr'>;
    }>;
    prediction?: Array<{
      exchange: 'polymarket' | 'kalshi';
      marketId?: string;
      title?: string;
      priceUsd?: number;
      url?: string;
      sources: Array<'text' | 'ocr'>;
    }>;
  };
};

type HistoryMedia = {
  url: string;
  type?: 'image' | 'video' | 'gif';
  thumbnail?: string;
};

type TweetUrl = {
  url: string;
  name?: string;
  tco?: string;
};

type TweetMention = {
  handle?: string;
  id?: string;
  name?: string;
};

type TweetContentKind = 'post' | 'reply' | 'quote' | 'retweet';

type TweetContent = {
  tweetId: string;
  kind: TweetContentKind;
  text: string;
  createdAt: number;
  author: TweetAuthor;
  link?: string;
  media?: HistoryMedia[];
  mentions?: TweetMention[];
  // Epoch ms from the realtime payload when the stored content includes it.
  receivedAt?: number;
  urls?: TweetUrl[];
  ref?: {
    type: 'reply' | 'quote' | 'retweet';
    tweetId?: string;
    text?: string;
    author?: TweetAuthor;
    media?: HistoryMedia[];
  };
};

type HistoricalContent = TweetContent | ProfileUpdateEvent | FollowEvent;

type HistoricalTweetResponse = {
  tweetId: string;
  twitterId: string;
  twitterHandle: string | null;
  body: string;
  time: string;
  // ISO persistence receive time for the history row.
  receivedTime: string;
  link: string;
  messageType: 'TWEET' | 'PROFILE' | 'FOLLOW';
  content: HistoricalContent;
  meta?: TweetMeta;
};

type HistoryResult = {
  data: HistoricalTweetResponse[];
  metadata: {
    count: number;
    handle?: string;
    handles?: string[];
    startDate?: string;
    endDate?: string;
    type?: 'TWEET' | 'PROFILE' | 'FOLLOW';
  };
};

Control Event Type

Handle-management responses use the control envelope family. These results arrive after follow or unfollow commands you send over the WebSocket.

type TwitterHandlesResult = {
  action: 'follow' | 'unfollow';
  requestId: string | null;
  results: Array<{
    input: string;
    handle?: string;
    name?: string;
    normalizedHandle?: string;
    profileImage?: string;
    twitterId?: string;
    state:
      | 'added'
      | 'already_following'
      | 'invalid_input'
      | 'duplicate'
      | 'not_found'
      | 'failed'
      | 'removed'
      | 'not_following';
    message?: string;
  }>;
  error: string | null;
};

Token Detection

TweetStream automatically detects tokens from multiple sources:

SourceExamples
$TICKER symbols$BTC, $ETH, $SOL
Contract addresses0x... (EVM), pump... (Solana)
DEX URLsdexscreener.com, birdeye.so
OCR textTickers and addresses in images

OCR Text Extraction

Text is automatically extracted from images attached to tweets. This includes:

  • Screenshots of charts and trading terminals
  • Images containing token information
  • Announcements and promotional graphics

OCR results are included in the meta.ocr.text field and are also scanned for token detection.

Prediction Markets

References to Polymarket markets are detected and enriched with live odds. The detected.prediction array includes:

  • Market ID and title
  • Current price (probability)
  • Direct link to the market

Example: Content Envelope

{
  "v": 1,
  "t": "tweet",
  "op": "content",
  "ts": 1702500000130,
  "d": {
    "tweetId": "1234567890",
    "kind": "post",
    "text": "MAKE AMERICA WEALTHY AGAIN!",
    "createdAt": 1702500000000,
    "receivedAt": 1702500000123,
    "author": {
      "id": "25073877",
      "handle": "@realDonaldTrump",
      "name": "Donald J. Trump",
      "platform": "truth_social",
      "followersCount": 9800000,
      "verifiedType": "government"
    },
    "link": "https://truthsocial.com/@realDonaldTrump/posts/114338322578075372"
  }
}

Example: Meta Envelope

{
  "v": 1,
  "t": "tweet",
  "op": "meta",
  "ts": 1702500001000,
  "d": {
    "tweetId": "1234567890",
    "ocr": {
      "text": "Chart showing SOL breakout at $100"
    },
    "detected": {
      "tokens": [
        {
          "symbol": "SOL",
          "name": "Solana",
          "priceUsd": 98.50,
          "sources": ["text", "ocr"]
        }
      ]
    }
  }
}

Example: Account Envelope

{
  "v": 1,
  "t": "account",
  "op": "profile_update",
  "ts": 1744156801000,
  "d": {
    "kind": "PROFILE",
    "eventId": "profile_1",
    "observedAt": 1744156800000,
    "receivedAt": 1744156800123,
    "actor": {
      "id": "123",
      "handle": "@tracked",
      "name": "Tracked Account",
      "profileImage": "https://pbs.twimg.com/profile_images/new-avatar_normal.jpg",
      "followersCount": 125000,
      "followingCount": 321,
      "verifiedType": "business",
      "verifiedLabel": {
        "badge": "https://pbs.twimg.com/affiliation_badge.jpg",
        "description": "Example Org",
        "url": "https://x.com/example"
      },
      "websiteUrl": "https://tracked.example",
      "location": "New York, NY"
    },
    "changes": {
      "avatar": "https://pbs.twimg.com/profile_images/new-avatar_normal.jpg",
      "bio": "Now watching markets 24/7",
      "websiteUrl": "https://new-site.example"
    },
    "previous": {
      "avatar": "https://pbs.twimg.com/profile_images/old-avatar_normal.jpg",
      "bio": "Old bio",
      "websiteUrl": "https://old-site.example"
    }
  }
}

Example: Control Envelope

{
  "v": 1,
  "t": "control",
  "op": "twitter_handles_result",
  "ts": 1744156802000,
  "d": {
    "action": "follow",
    "requestId": "req_123",
    "results": [
      {
        "input": "newaccount",
        "handle": "@newaccount",
        "normalizedHandle": "@newaccount",
        "twitterId": "456",
        "state": "added",
        "message": "Now tracking @newaccount"
      }
    ],
    "error": null
  }
}

Example: History Response

{
  "data": [
    {
      "tweetId": "follow_1",
      "twitterId": "123",
      "twitterHandle": "tracked",
      "body": "Followed @newaccount",
      "time": "2026-04-09T01:00:00.000Z",
      "receivedTime": "2026-04-09T01:00:00.500Z",
      "link": "https://x.com/newaccount",
      "messageType": "FOLLOW",
      "content": {
        "kind": "FOLLOW",
        "eventId": "follow_1",
        "observedAt": 1744160400000,
        "actor": {
          "id": "123",
          "handle": "tracked",
          "name": "Tracked Account",
          "followersCount": 125000,
          "followingCount": 321,
          "verifiedType": "business"
        },
        "target": {
          "id": "456",
          "handle": "newaccount",
          "name": "New Account",
          "profileImage": "https://pbs.twimg.com/profile_images/newaccount_normal.jpg",
          "websiteUrl": "https://newaccount.example"
        }
      }
    }
  ],
  "metadata": {
    "count": 1,
    "handle": "tracked",
    "handles": ["tracked"],
    "startDate": "2026-04-01T00:00:00.000Z",
    "endDate": "2026-04-30T23:59:59.999Z",
    "type": "FOLLOW"
  }
}

Start real-time Twitter WebSocket alerts today

WebSocket delivery, OCR, and token detection - no infrastructure to build.

From $199/mo · Minimum/Pro 3-day trial · OCR + token detection included

Related Pages