Your multistream chat, finally in one place. 🎯
One unified chat for Twitch, YouTube, and Kick in a single clean feed.
- Reads Twitch chat via EventSub WebSocket
- Reads YouTube live chat via YouTube Live Streaming API
- Reads Kick chat via webhooks
- Displays everything in one feed as
timestamp + platform + Name: Message - Twitch emotes render as inline images
- Emote picker for composing messages with your Twitch emotes
- Reply field that sends to Twitch only
- Popout chat window for a clean, standalone view
- Clear chat button to wipe message history between streams
- Optional password protection for public access (LAN access stays open)
- Stores recent messages in local SQLite for reload/restart
Both YouTube and Kick require a public HTTPS URL for OAuth callbacks and webhooks — expose your local server (port 8090) behind a public HTTPS endpoint (e.g. a reverse proxy or Cloudflare Tunnel).
Then set APP_BASE_URL in .env to that public HTTPS URL. This URL is used to derive the default redirect URIs for YouTube and Kick.
cd ~/unified-chat
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Fill in your credentials (see sections below)Start the server temporarily (stops when you close the terminal or press Ctrl+C):
venv/bin/uvicorn unified_chat.main:app --host 0.0.0.0 --port 8090Then open your public URL or http://YOUR_LAN_IP:8090.
If you want password protection on the UI, set LOGIN_PASSWORD_HASH and SESSION_SECRET_KEY in .env. With auth enabled, unified-chat will require login on public hosts such as unified-chat.domain.com, but it will still allow direct local/private access on localhost, 127.0.0.1, and LAN IPs like 192.168.x.x without a password. Keep SESSION_COOKIE_SECURE=true for the public HTTPS domain.
If you already run stream-control, you can reuse your existing credentials.
| Variable | Where to find it |
|---|---|
TWITCH_CLIENT_ID |
Twitch Developer Console > Your App > Client ID |
TWITCH_BROADCASTER_ID |
Your numeric Twitch user ID (see below) |
TWITCH_TOKENS_PATH |
Path to twitch_tokens.json managed by stream-control |
How to find your Twitch Broadcaster ID:
- Go to https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
- Enter your Twitch username
- Copy the numeric User ID (e.g.
424350540)
Unified Chat reads
twitch_tokens.jsonbut never writes to it. Token refresh is handled bystream-control. For the hype train bar and API backfill after refresh/reconnect, the shared token must also includechannel:read:hype_train. After adding that scope instream-control, re-authorize so Twitch issues a token with the updated scope set.
YouTube uses Google OAuth 2.0. You need to create a Google Cloud project and download OAuth credentials. This is a one-time setup -- after the initial authorization, tokens refresh automatically and never expire (as long as you follow step 6 below).
- Go to Google Cloud Console
- Click the project dropdown (top-left) > New Project
- Name it something like
Unified Chatand click Create - Make sure the new project is selected in the dropdown
- In the top search bar, search for
YouTube Data API v3 - Click the result and press Enable
After creating the project you'll land on the Google Auth Platform page.
- Click Branding in the left sidebar
- Fill in the required fields:
- App name:
Unified Chat(or whatever you like) - User support email: your email
- Developer contact email: your email (scroll to bottom)
- App name:
- Click Save
- Click Audience in the left sidebar
- Select External and click Save
- Under Test users, click + Add users
- Enter the Google/YouTube email address you'll use for streaming
- Click Save
- Click Data Access in the left sidebar
- Click + Add or Remove Scopes
- Search for
youtube.readonlyand check it - Click Update then Save
- Click Clients in the left sidebar
- Click + Create Client
- Application type: Web application
- Name:
Unified Chat(or whatever you like) - Under Authorized redirect URIs, click + Add URI and enter your public URL:
https://your-public-domain/auth/youtube/callback - Click Create
- On the client details page, click Download JSON (top right)
- Save the downloaded file as
google-client-secret.jsonin the unified-chat project root
APP_BASE_URL=https://your-public-domain
YOUTUBE_CLIENT_SECRETS_FILE=/path/to/unified-chat/google-client-secret.jsonYOUTUBE_REDIRECT_URI defaults to {APP_BASE_URL}/auth/youtube/callback, so it will automatically use your public HTTPS URL. You can override it explicitly if needed.
This is the most important step. Without this, your refresh token expires after 7 days and you'll need to re-authorize constantly.
- Click Audience in the left sidebar
- Under Publishing status, click Publish App
- Confirm by clicking Confirm
You do not need Google verification for apps with fewer than 100 users. The app will show an "unverified app" warning during first authorization -- this is normal. Users click Advanced > Go to Unified Chat (unsafe) to proceed.
- Start Unified Chat temporarily:
cd ~/unified-chat venv/bin/uvicorn unified_chat.main:app --host 0.0.0.0 --port 8090
- Visit
https://your-public-domain/auth/youtube/startin your browser - Sign in with Google and grant access
- Done -- tokens are saved and will auto-refresh forever. Press
Ctrl+Cto stop the server.
Kick uses OAuth 2.0 with webhooks for real-time chat messages. The recommended setup uses app-token mode which is fully "set and forget" -- no user authorization needed.
Important: Kick webhooks require a public HTTPS URL. If you're running locally, you'll need a reverse proxy or tunnel (e.g. Cloudflare Tunnel, ngrok).
- Go to Kick Developer Portal
- Click Create App
- Fill in the required fields:
- App name:
Unified Chat - Redirect URI:
https://your-public-domain/auth/kick/callback - Webhook URL:
https://your-public-domain/webhooks/kick
- App name:
- Save the app
From your Kick app page, copy:
- Client ID > paste into
KICK_CLIENT_IDin.env - Client Secret > paste into
KICK_CLIENT_SECRETin.env
You need your numeric Kick user ID for app-token mode.
- Go to your Kick channel page (e.g.
https://kick.com/yourname) - Open browser DevTools (F12) > Console
- Run:
fetch('/api/v2/channels/yourname').then(r=>r.json()).then(d=>console.log('User ID:', d.user_id))
- Copy the numeric user ID
- Paste it into
KICK_BROADCASTER_USER_IDin.env
KICK_CLIENT_ID=your_client_id_here
KICK_CLIENT_SECRET=your_client_secret_here
KICK_BROADCASTER_USER_ID=your_numeric_user_idWith KICK_BROADCASTER_USER_ID set, the app uses client credentials mode -- no manual authorization needed. Tokens are generated on-demand and never expire.
Both the redirect URI and webhook URL use your public HTTPS domain set in
APP_BASE_URL.
A ready-to-install unit file is provided in unified-chat.service for the current default layout (/home/<user>/unified-chat on port 8090).
sudo cp unified-chat.service /etc/systemd/system/unified-chat.service
sudo systemctl daemon-reload
sudo systemctl start unified-chatIf your username or paths differ, edit unified-chat.service first or use unified-chat.service.example as a generic template.
stream-control can manage unified-chat.service automatically, so you do not need to enable it at boot unless you explicitly want it always running.
- Unified Chat does not refresh Twitch tokens -- that's handled by
stream-control - Replies go to Twitch only
- YouTube and Kick messages are not cross-posted anywhere
- Kick webhooks require a public HTTPS URL
- Emote rendering is Twitch-only (Kick webhooks don't include emote data)