Twitch Chat Firehose

A public, real-time stream of chat messages and moderation events from the Twitch channels CatQuery currently monitors.

WSS wss://firehose.catquery.com/firehose

Watch it live

Connect a client

The default stream (no query parameters, and explicitly ?jsonBasic=true or ?json=true) sends the structured JSON payload described below. Add ?raw=true to receive the original Twitch IRC wire line as plain text instead. Each of these also accepts =1 in place of =true.

Browser JavaScript (JSON, the default)

const socket = new WebSocket("wss://firehose.catquery.com/firehose");

socket.addEventListener("open", () => {
  console.log("Connected to the CatQuery firehose");
});

socket.addEventListener("message", (event) => {
  const message = JSON.parse(event.data);
  console.log(`#${message.channel} <${message.displayName}> ${message.text}`);
});

socket.addEventListener("close", () => {
  console.log("Disconnected; reconnect with backoff if needed");
});

Node.js with ws (raw IRC)

import WebSocket from "ws";

const socket = new WebSocket("wss://firehose.catquery.com/firehose?raw=true");

socket.on("message", (data) => {
  console.log(data.toString());
});
The server sends standard WebSocket ping frames every 30 seconds. Browsers and most WebSocket libraries answer automatically; low-level clients must reply with a pong.

Payload format

By default (and with ?jsonBasic=true or ?json=true), every WebSocket message is one JSON object with this shared shape. With ?raw=true, the server instead sends the raw IRC line as plain text.

{
  "text": "HOORAY",
  "username": "foobar",
  "displayName": "FooBar",
  "channel": "xqc",
  "timestamp": "2025-11-22T06:11:26.537Z",
  "id": "8939f358-4118-4fb6-86ee-4bba89a1de8e",
  "tags": {
    "color": "#000000",
    "badges": "subscriber/12",
    "emotes": "25:0-4",
    "room-id": "29400754",
    "user-id": "60458761",
    "first-msg": "0"
  }
}
FieldTypeMeaning
textstringChat text or a human-readable event description.
usernamestring, optionalLowercase Twitch login. It can be absent on moderation events.
displayNamestringThe sender's display name, or the affected username for a moderation event.
channelstringLowercase channel login, without #.
timestampstringUTC timestamp in ISO 8601 format.
idstringTwitch message ID. Moderation events currently use an empty string.
tagsobjectString-valued Twitch IRC tags. Available keys depend on the event type; missing optional values may be empty strings.

Useful chat tags include badges, badge-info, color, emotes, first-msg, room-id, and user-id. Treat unknown tags as forward-compatible additions.

Event types

Chat messages

Normal Twitch PRIVMSG events have a non-empty id, a username, and the standard chat tags shown above.

User notices

Subscriptions, raids, announcements, and similar Twitch USERNOTICE events have tags["event-type"] === "usernotice". When system-msg is nonempty, text starts with that system message, followed by one separator space if user-entered text is present. Announcements with an empty system-msg contain only the user-entered text, with its whitespace preserved. Original Twitch tags and emote positions are retained; emote positions refer to the user-entered text, excluding any system-message prefix.

Timeouts and permanent bans

Moderation CLEARCHAT events have an empty id, no username, and include target-user-id. A non-empty ban-duration means a timeout; an empty value means a permanent ban.

function eventType(event) {
  if (event.tags["event-type"] === "usernotice") return "usernotice";
  if (event.id === "" && event.tags["target-user-id"]) {
    return event.tags["ban-duration"] ? "timeout" : "ban";
  }
  return "message";
}

Usage and reliability notes

  • This is a live broadcast, not a history API. Events sent before you connect are not replayed.
  • The stream covers channels monitored by CatQuery; it is not every message sent across all of Twitch.
  • Connections can close during deployments, network interruptions, or if a client cannot consume data quickly enough. Reconnect with exponential backoff and jitter.
  • Each IP address is limited to ten simultaneous connections and 100 new connections per minute. Exceeding either limit triggers escalating temporary blocking.