Developer API
Read the unified chat stream over one WebSocket to build bots, moderation tools, vote counters, analytics, alerts — anything.
Just setting up your overlay? See the streamer guide →
All-Chat aggregates live chat from Twitch, YouTube, Kick, TikTok and Discord into a single normalized stream — every message, plus platform events like subs, bits, raids, super chats and gifts, in one format, with 7TV/BTTV/FFZ emotes resolved for you. The same stream that powers the browser overlay is the one your tool reads.
Open a WebSocket to the overlay endpoint. Reading the stream is anonymous— no token or account is required (this is the same "OBS mode" the browser overlay uses):
wss://allch.at/ws/overlay/{overlay_id}Your overlay_idis the UUID in your overlay's browser-source URL. Want to try without an account first? Use the public test overlay.
| Field | Type | Description |
|---|---|---|
| ?token= | JWT (optional) | Owner token. Only needed for owner-scoped access; omit it for read-only consumption. |
| ?since= | int (optional) | Milliseconds since the Unix epoch. On connect, the server replays buffered messages newer than this timestamp so a reconnecting client can backfill the gap. Omit to start live. |
const ws = new WebSocket(
"wss://allch.at/ws/overlay/00000000-0000-4000-8000-000000000a11"
)
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
switch (msg.type) {
case "chat_message":
case "message_update": {
const d = msg.data
if (d.event) {
console.log("event", d.platform, d.event.type, d.event.value?.display_text)
} else {
console.log("chat", d.platform, d.user.display_name + ":", d.message.text)
}
break
}
case "ping":
ws.send(JSON.stringify({ type: "pong" }))
break
case "connected":
case "platform_status":
break
}
}# pip install websocket-client
import json, websocket
def on_message(ws, raw):
msg = json.loads(raw)
if msg["type"] in ("chat_message", "message_update"):
d = msg["data"]
if d.get("event"):
print("event", d["platform"], d["event"]["type"])
else:
print("chat", d["platform"], d["user"]["display_name"], d["message"]["text"])
elif msg["type"] == "ping":
ws.send(json.dumps({"type": "pong"}))
ws = websocket.WebSocketApp(
"wss://allch.at/ws/overlay/00000000-0000-4000-8000-000000000a11",
on_message=on_message,
)
ws.run_forever()Every frame is JSON with the same envelope:
{
"type": "chat_message",
"data": { ... }, // shape depends on "type" (omitted for ping/pong)
"timestamp": "2026-06-18T14:30:00Z"
}| Field | Type | Description |
|---|---|---|
| type | string | Message type (see below). |
| data | object | Payload; shape depends on type. Omitted for ping/pong. |
| timestamp | string | RFC 3339 timestamp of when the gateway sent the frame. |
type is one of:
| Field | Type | Description |
|---|---|---|
| chat_message | A chat message or a platform event. data is the unified message object. | |
| message_update | An update to a previously sent message (e.g. TikTok like aggregates). Same data shape as chat_message. | |
| connected | Sent once on connect: { overlay_id, message }. | |
| platform_status | Connection status of a source platform. | |
| ping | Heartbeat from the server. Reply with { "type": "pong" }. | |
| error | Error notice: { code, message }. |
For chat_message and message_update, data is the unified message object:
| Field | Type | Description |
|---|---|---|
| id | string | Unique message ID. |
| overlay_id | string | Overlay this message was delivered to. |
| platform | string | "twitch" | "youtube" | "kick" | "tiktok" | "discord". |
| channel_id | string | Platform channel identifier. |
| channel_name | string | Human-readable channel name. |
| user | object | Author info (see below). |
| message | object | { text, emotes[], attachments[]? } (see below). |
| timestamp | string | RFC 3339 message time (UTC). |
| metadata | object | Free-form, platform-specific extras. |
| event | object? | Present only when the message is a platform event (see Events). Absent for normal chat. |
| Field | Type | Description |
|---|---|---|
| id | string | Platform user ID. |
| username | string | Login/handle. |
| display_name | string | Display name. |
| color | string? | Name color, hex (e.g. "#FF0000"). |
| badges | Badge[] | Author badges (see below). |
| avatar_url | string? | Profile image URL when available. |
| pronouns | string? | e.g. "she/her" when known. |
| name_gradient | string? | Optional gradient descriptor (JSON string). |
| source_badges / source_user_id | ? | Origin-channel identity for shared-chat messages. |
| avatar_frame_url / avatar_flair_url | string? | Cosmetic frame/flair when set. |
message is { "text": string, "emotes": Emote[], "attachments"?: Attachment[] }. Each Emote:
| Field | Type | Description |
|---|---|---|
| code | string | Emote text token, e.g. "Kappa". |
| provider | string | "twitch" | "7tv" | "bttv" | "ffz" | "discord" | platform. |
| url | string | CDN image URL. |
| positions | int[][] | Array of [start, end] index pairs into text where the emote occurs. |
attachments is present only when a message carries media (Discord image/GIF/video uploads and Tenor/Giphy link previews today). Each Attachment:
| Field | Type | Description |
|---|---|---|
| type | string | "image" or "video". GIFs are images that animate. |
| url | string | Media URL. |
| content_type | string? | MIME type, e.g. "image/gif", "video/mp4". |
| width / height | int? | Intrinsic pixel dimensions when known. |
| thumb_url | string? | Poster frame for videos when available. |
| spoiler | bool? | True when the sender marked the media a spoiler. |
| filename | string? | Original filename (used for alt text). |
Each Badge is { "name", "version", "icon_url" } — e.g. { "name": "subscriber", "version": "12", "icon_url": "..." }.
{
"type": "chat_message",
"data": {
"id": "abc123",
"overlay_id": "00000000-0000-4000-8000-000000000a11",
"platform": "twitch",
"channel_id": "12345",
"channel_name": "examplestreamer",
"user": {
"id": "67890",
"username": "viewer123",
"display_name": "Viewer123",
"color": "#1E90FF",
"badges": [{ "name": "subscriber", "version": "12", "icon_url": "https://.../sub.png" }]
},
"message": {
"text": "that was insane PogChamp",
"emotes": [
{ "code": "PogChamp", "provider": "twitch", "url": "https://.../pogchamp.png", "positions": [[13, 20]] }
]
},
"timestamp": "2026-06-18T14:30:00Z",
"metadata": {}
},
"timestamp": "2026-06-18T14:30:00Z"
}Platform events (subs, bits, raids, donations, …) arrive as chat_message frames whose data includes an event object. Normal chat has no event field.
| Field | Type | Description |
|---|---|---|
| type | string | Event type (see list below). |
| tier | string | Relative prominence: "high" | "medium" | "low". |
| value | object? | { amount: number, currency: string, display_text: string } — e.g. amount 250, currency "bits", display_text "250 bits". |
| duration | int | Suggested on-screen display time, seconds. |
| is_update | bool | true when this updates a prior event (e.g. TikTok like aggregates, delivered as message_update). |
| aggregation_id | string? | Groups successive updates of the same aggregate. |
| metadata | object? | Event-specific raw fields. |
subscription, resubscription, gift_subscription, mystery_gift, bits, raid, channel_points, message_deletionsuper_chat, super_sticker, new_sponsor, member_milestone, membership_gift, gift_receivedgift, like_aggregate, follow, share{
"type": "chat_message",
"data": {
"id": "evt-sub-1",
"overlay_id": "00000000-0000-4000-8000-000000000a11",
"platform": "twitch",
"channel_id": "12345",
"channel_name": "examplestreamer",
"user": { "id": "999", "username": "newsub", "display_name": "NewSub", "badges": [] },
"message": { "text": "just subscribed!", "emotes": [] },
"timestamp": "2026-06-18T14:35:00Z",
"metadata": {},
"event": {
"type": "subscription",
"tier": "medium",
"duration": 8,
"is_update": false,
"value": { "amount": 1, "currency": "months", "display_text": "Tier 1 sub" }
}
},
"timestamp": "2026-06-18T14:35:00Z"
}On connect the server sends a connected frame, then a platform_status frame per configured source so your UI can show indicators immediately. platform_status data:
| Field | Type | Description |
|---|---|---|
| platform | string | Source platform. |
| channel_id | string | Source channel. |
| channel_name | string? | Human-readable channel name. |
| status | string | "connected" | "reconnecting" | "offline" | "quota_exceeded". |
| next_retry_at | string? | RFC 3339 time of the next reconnect attempt, when applicable. |
| error_message | string? | Human-readable detail when degraded. |
{ "type": "ping" }. Reply with { "type": "pong" }. (Most WebSocket libraries also answer low-level ping frames automatically.)?since=<ms-epoch> set to just before you lost the connection — the server replays buffered messages newer than that timestamp.There is a public test overlay that streams realistic fake traffic — random chat, poll votes (the literal messages 1, 2, 3, 4) and platform events — so you can build and validate an integration without an account or a real channel. Just connect; traffic flows while a client is connected and stops when the last one disconnects:
wss://allch.at/ws/overlay/00000000-0000-4000-8000-000000000a11Drop that URL into either example above and you should immediately see messages and events stream in.