A developer-friendly playground for building custom Twitch event displays. Self-hosted Node.js app that receives Twitch events (raids, follows, subs, gifts, cheers, redemptions, chat) via EventSub WebSocket and delivers them to a customizable scratch.html page for use in OBS Browser Sources.
- Real-time Twitch Events - Raids, follows, subs, gift subs, cheers, channel point redemptions, and chat
- Rich Event Data - Profile images, broadcaster types (partner/affiliate), account ages, stream info, and more
- Two Setup Modes:
- Easy Mode - OAuth handled by hosted proxy (just download and run)
- Self-Hosted Mode - Create your own Twitch app for maximum privacy
- Event History - View past 50 events with statistics and filters
- Mock Event Testing - Test your overlays without needing real Twitch events
- Privacy-First - Tokens stored locally in
.tokens.json, never leave your computer (except during OAuth exchange in Easy Mode) - OBS Browser Source Ready - Use
scratch.htmldirectly in OBS to display custom notifications - Developer-Friendly - Built for creative coders who want full control over their stream overlays
- Backend: Node.js, Express, Socket.io, @twurple (auth, api, eventsub-ws, chat)
- Frontend: Vanilla JavaScript, CustomEvent API for event dispatching
- Code Style: JavaScript Standard (no semicolons, single quotes)
- Node.js (v18 or higher recommended)
- A Twitch account
- Basic terminal/command line knowledge
git clone https://github.com/dinir/ttv-toaster.git
cd ttv-toaster
npm install-
Copy
.env.exampleto.env:cp .env.example .env
-
Edit
.envand uncomment theOAUTH_PROXY_URLline:OAUTH_PROXY_URL=https://toaster.dinir.works
-
Start the server:
npm start
-
Open
http://localhost:3000in your browser and click "Login with Twitch"
-
Create a Twitch application:
- Go to Twitch Developer Console
- Click "Register Your Application"
- Name: Choose any name (e.g., "My TTV Toaster")
- OAuth Redirect URLs:
http://localhost:3000/auth/callback - Category: Choose "Application Integration"
- Click "Create"
- Copy your Client ID and generate a Client Secret
-
Copy
.env.exampleto.env:cp .env.example .env
-
Edit
.envand add your credentials:TWITCH_CLIENT_ID=your_client_id_here TWITCH_CLIENT_SECRET=your_client_secret_here
-
Start the server:
npm start
-
Open
http://localhost:3000in your browser and click "Login with Twitch"
After logging in, the main page provides test buttons for all event types:
- Click any test button to trigger mock events (no real Twitch events needed!)
- Perfect for testing your custom displays and styling
- All test events are recorded in event history
Real Events - Test with actual Twitch events:
- Follow your own channel from another account
- Send chat messages starting with
!(default filter) - Trigger other events on your live stream
- In OBS, add a new Browser Source
- Set URL to:
http://localhost:3000/scratch.html - Set Width/Height to your canvas size (e.g., 1920x1080)
- Check "Shutdown source when not visible" for better performance
- Customize
scratch.htmlto create your own designs!
For detailed OBS setup, see OBS_GUIDE.md
All events are dispatched as CustomEvents on the document object. See EVENT_DATA.md for complete reference.
| Event Type | Trigger | Key Data |
|---|---|---|
twitch:raid |
Someone raids your channel | displayName, viewerCount, gameName, profileImageUrl |
twitch:follow |
Someone follows | displayName, createdAt, profileImageUrl |
twitch:subscribe |
Someone subscribes | displayName, tier, isGift, profileImageUrl |
twitch:gift |
Someone gifts subs | displayName, amount, tier, cumulativeAmount |
twitch:cheer |
Someone cheers bits | displayName, bits, message, profileImageUrl |
twitch:redemption |
Channel points redeemed | displayName, rewardTitle, rewardCost, userInput |
twitch:chat |
Chat message (filtered) | displayName, message, isMod, isSubscriber |
document.addEventListener('twitch:raid', (event) => {
const { displayName, viewerCount, gameName, profileImageUrl } = event.detail
// Create your custom notification
console.log(`${displayName} raided with ${viewerCount} viewers!`)
console.log(`They were playing: ${gameName}`)
// Display profile image
const img = document.createElement('img')
img.src = profileImageUrl
document.body.appendChild(img)
})See EVENT_DATA.md for complete examples and tips.
GET /api/events- Get all events (optional?limit=50parameter)GET /api/events/:type- Get events by type (raid, follow, subscribe, etc.)GET /api/events-stats- Get event statisticsPOST /api/events/clear- Clear all event history
By default, only chat messages starting with ! are sent to scratch.html to prevent spam. Configure filters in .chat-filters.json or via the API:
{
"enabled": true,
"prefixFilters": ["!"],
"blockList": []
}API Endpoints:
GET /api/chat/filters- Get current filtersPOST /api/chat/filters- Update filters
POST /api/test/:eventType- Trigger a mock event (raid, follow, subscribe, gift, cheer, redemption, chat)
ttv-toaster/
├── src/
│ └── backend/
│ ├── server.js # Main server
│ ├── twitch/
│ │ ├── auth.js # OAuth & token management
│ │ ├── eventListener.js # EventSub handlers
│ │ └── chatListener.js # Chat handlers
│ └── bridges/
│ └── eventBridge.js # Socket.io bridge
├── public/
│ ├── index.html # Setup/login page
│ └── scratch.html # Event display page (customize this!)
├── .env # Your configuration
├── .tokens.json # Auth tokens (auto-generated)
├── .chat-filters.json # Chat filters (optional)
└── EVENT_DATA.md # Event data reference
Check out the /examples directory for ready-to-use templates:
- View past 50 events with statistics
- Filter by event type
- Auto-refreshes every 5 seconds
- Perfect for reviewing stream highlights
- Shows all events with profile pictures
- Partner/affiliate badges
- Account age warnings
- Perfect for testing and development
- Full-screen raid notifications
- Animated entrance/exit
- Shows game raider was streaming
- Clean bottom-right notifications
- Queue system for multiple events
- Works with all event types
Try them: Links available on the main page after logging in, or add to OBS as a Browser Source!
The public/scratch.html file is your blank canvas:
- Minimal boilerplate (just Socket.io + event dispatcher)
- Transparent background (OBS-ready)
- Comments showing available events
- One example handler to get you started
Tips for customization:
- Start with an example template or build from scratch
- Use
event.detail.profileImageUrlfor profile pictures - Add CSS animations for eye-catching transitions
- Play sounds with
new Audio('sound.mp3').play() - Filter events (e.g., raids with 10+ viewers only)
- Show partner badges, account ages, game info, etc.
See EVENT_DATA.md for all available event properties!
Easy Mode:
- Verify
.envhasOAUTH_PROXY_URLuncommented - Check that the URL doesn't have a trailing slash
- Restart the server after editing
.env
Self-Hosted Mode:
- Double-check your Client ID and Secret from Twitch Developer Console
- Ensure redirect URI is exactly
http://localhost:3000/auth/callback - Make sure there are no extra spaces in your
.envfile
- Check browser console at
http://localhost:3000/scratch.htmlfor errors - Verify server console shows "EventSub WebSocket connected"
- Test with a simple event like chat (send a message starting with
!) - Make sure you're testing on the authenticated Twitch account
- Test the URL in a regular browser first
- Right-click the Browser Source → Interact → Open DevTools to check for errors
- Ensure OBS Browser Source has correct dimensions
- Try toggling "Shutdown source when not visible" off temporarily
- By default, only messages starting with
!are sent - Check
.chat-filters.jsonor useGET /api/chat/filtersto see current config - Disable filtering:
POST /api/chat/filterswith{"enabled": false}
Error: listen EADDRINUSE: address already in use :::3000- Port 3000 is already in use
- Change
PORT=3000in.envto another port (e.g.,PORT=3001) - Or stop the other process using port 3000
- The app automatically refreshes tokens
- If refresh fails, click "Disconnect" at
http://localhost:3000and re-authenticate - In Self-Hosted Mode, verify your Client Secret is correct
This project uses JavaScript Standard Style:
npm run lint # Check for style issues
npm run lint:fix # Auto-fix style issuesFor development, consider using nodemon:
npm install -g nodemon
nodemon src/backend/server.js- Easy Mode: Tokens are exchanged via Railway-hosted proxy, then stored locally
- Self-Hosted Mode: All authentication happens locally, no third-party services
- Tokens are stored in
.tokens.json(gitignored by default) - Never commit
.envor.tokens.jsonto version control - Railway proxy source code is in
oauth-proxy/for transparency
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow JavaScript Standard style (
npm run lint) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
ISC License - See LICENSE for details
Built with Twurple - The powerful Twitch API library for Node.js
- GitHub Issues - Bug reports and feature requests
- OBS_GUIDE.md - OBS integration guide
- EVENT_DATA.md - Complete event data reference
- Twurple Documentation - API library docs
- Twitch EventSub Reference - Official Twitch docs
Happy streaming! If you build something cool with TTV Toaster, share it with the community!