Twitch Channel Points overlay bot — let viewers spend points to show GIFs or play sound clips on your stream via an OBS Browser Source. Fully local, no cloud dependencies.
Viewers redeem Channel Points rewards on Twitch to trigger on-stream media:
- "Show a GIF" — paste a direct Giphy link; the GIF appears on your OBS overlay for a configurable duration.
- "Play a Sound" — type a sound name from a local library; the clip plays through the OBS Browser Source.
Both reward types share a single FIFO queue with cooldown, so media never overlaps. Invalid requests are auto-cancelled and points refunded.
Important
Python 3.10 or later is required. This project uses uv for dependency management.
# 1. Clone the repo
git clone https://github.com/matteo/visema.git
cd visema
# 2. Install dependencies (creates a .venv automatically)
uv sync
# 3. Copy example config and edit it
cp config.yaml.example config.yaml
# Edit config.yaml — set reward names to match your Twitch Channel Points rewards
# 4. Add sound files to the sounds/ folder
cp my_sounds/*.mp3 sounds/
# 5. Run — first launch triggers Device Code authentication
uv run visemaOn first run, Visema prints a device code and URL. Open the URL in a browser, enter the code, and authorize the app. Tokens are saved automatically for future runs.
- In OBS, add a Browser Source.
- Set URL to:
http://127.0.0.1:9876/overlay - Set width/height to your canvas (e.g. 1920×1080).
- Enable "Allow transparency".
- Disable "Shutdown source when not visible".
The overlay connects automatically via WebSocket — no additional configuration needed.
Note
Audio played through the Browser Source routes to OBS's audio pipeline. Make sure the Browser Source track is unmuted in the Audio Mixer.
# Optional — set only if you fork with your own Twitch app
# TWITCH_CLIENT_ID=your_client_id_here| Section | Key | Default | Description |
|---|---|---|---|
twitch |
reward_gif |
"Mostra una GIF" |
Exact name of the GIF Channel Points reward |
twitch |
reward_sound |
"Suona Suono" |
Exact name of the sound Channel Points reward |
twitch |
reward_gif_cost |
500 |
Points cost for GIF redeem |
twitch |
reward_sound_cost |
300 |
Points cost for sound redeem |
gif |
allowed_domains |
Giphy CDN domains | Domains accepted for GIF URLs |
gif |
display_duration_seconds |
8 |
How long a GIF is shown (seconds) |
gif |
cooldown_seconds |
3 |
Wait time between queue items |
gif |
max_queue_size |
5 |
Maximum items in the queue |
gif |
size_percent |
40 |
GIF width as % of overlay canvas |
audio |
sounds_dir |
"sounds" |
Path to local audio library folder |
audio |
volume |
1.0 |
Playback volume (0.0–1.0) |
overlay |
port |
9876 |
HTTP/WebSocket server port |
overlay |
position |
"center" |
Overlay position (center, bottom-left, etc.) |
By default, Visema runs with a single Twitch account (the broadcaster). For separation of concerns, you can use a dedicated bot account for chat messages:
# First authenticate the broadcaster (single mode)
uv run visema
# Then run with a separate bot account
uv run visema --botThe --bot flag requires the broadcaster to already be authenticated (token_broadcaster.json must exist). On first dual-account run, Visema triggers a second Device Code Flow for the bot.
Visema only accepts direct CDN links from Giphy — not page URLs.
How viewers get a valid link:
- Go to giphy.com and find a GIF.
- Right-click the GIF → "Copy Image Address".
- Paste the URL in the Channel Points redeem input.
Valid URLs start with i.giphy.com or media*.giphy.com. Page URLs like giphy.com/gifs/... are rejected automatically and points are refunded.
Audio files are managed locally by the streamer — viewers cannot upload or inject arbitrary audio.
- Place
.mp3,.ogg, or.wavfiles in thesounds/folder. - On startup, Visema builds an index from filenames (lowercased, extension stripped).
- Viewers request sounds by name —
"vine boom","vine_boom", and"VINE BOOM"all resolve to the same file.
Use the !soundlist chat command to see all available sounds.
All commands are read-only and respond in chat:
| Command | Description |
|---|---|
!gif |
Instructions for finding a valid Giphy direct link |
!sound |
How to use the sound redeem + reminder to use !soundlist |
!soundlist |
Lists all available sounds from the sounds/ folder |
Response text is configurable in config.yaml under the commands: section.
visema/
├── .env # secrets (broadcaster ID, optional client credentials)
├── config.yaml # runtime settings (reward names, queue, overlay)
├── pyproject.toml # project metadata and dependencies
├── sounds/ # local audio library — add MP3/OGG/WAV files here
│ ├── boom.mp3
│ └── oof.mp3
├── visema/
│ ├── main.py # entrypoint — wires everything together
│ ├── twitch/
│ │ ├── auth.py # Device Code Flow authentication, token persistence
│ │ ├── eventsub.py # EventSub listener for Channel Points redemptions
│ │ └── chat.py # EventSub-based chat command listener
│ ├── server/
│ │ ├── app.py # FastAPI: HTTP routes, static files, WebSocket
│ │ └── ws_manager.py # active WebSocket connection tracking and broadcasting
│ ├── overlay/
│ │ ├── index.html # OBS Browser Source page
│ │ ├── overlay.js # receives WS events, renders GIFs, plays audio
│ │ └── overlay.css # transparent background, positioning, animations
│ ├── media/
│ │ ├── validator.py # GIF URL validation + sounds directory index
│ │ └── queue.py # unified FIFO queue with cooldown
│ └── utils/
│ └── config.py # loads .env and config.yaml into typed settings
└── tests/
├── test_validator.py
└── test_queue.py
- Viewer redeems the GIF reward on Twitch with a Giphy CDN URL as input.
- Visema validates the URL against allowed domains (
i.giphy.com,media*.giphy.com). - Valid → enqueued in the media queue. Invalid → redemption cancelled, points refunded.
- Queue worker broadcasts the GIF payload to all connected OBS overlays via WebSocket.
- Overlay renders the GIF, holds for
display_duration_seconds, then fades out. - After
cooldown_seconds, the next queued item is processed.
- Viewer redeems the sound reward with a sound name as input (e.g.
"boom"). - Visema normalises the input and looks it up in the sounds index.
- Found → enqueued. Not found → redemption cancelled, points refunded.
- Queue worker broadcasts the audio payload to OBS overlays.
- Overlay plays the sound through the Web Audio API at configured volume.
- On
endedevent, the overlay sends an ack back so the queue starts its cooldown.
- Skip command — add a
!visemaskipchat command (moderator only) that advances the queue. - Per-user cooldown — track last redemption time per viewer to prevent spam.
- Blocklist — add a
blocked_userslist inconfig.yaml; redemptions are auto-cancelled. - Hot-reload sounds — watch
sounds/for file changes and rebuild the index at runtime.
