A production-ready, self-hosted Go service that bridges Twitch EventSub and YouTube WebSub events with your notification systems. Get instant, reliable notifications when your favorite streamers go live across multiple platforms with rich metadata and flexible delivery options.
- Key Features
- Quick Start
- Configuration
- Webhook Groups
- Webhook Payload
- CLI Commands
- Development
- Docker
- Architecture
- Contributing
- Real-time Stream Notifications: Instant notifications via Twitch EventSub and YouTube WebSub
- Multi-Platform Support: Monitor Twitch and YouTube streams from a single service
- Automatic User ID Resolution: Configure streamers with just their login name - user IDs are resolved automatically
- Smart Webhook Dispatching: Send notifications to multiple endpoints with custom payloads
- Rich Metadata Enrichment: Automatically fetch streamer info (follower count, profile images, descriptions)
- Robust Retry Logic: Exponential backoff for failed webhook deliveries with persistent state
- Duplicate Detection: Built-in deduplication prevents spam notifications
- HMAC Signature Validation: Secure webhook verification and optional payload signing
- Graceful Error Handling: Continues operation even when external services fail
- Efficient YouTube Integration: WebSub-first approach minimizes API quota usage
- HTTPS Support: Let's Encrypt integration for secure webhook endpoints
- OpenTelemetry Integration: Built-in observability with metrics and distributed tracing
- File Output: JSON logging for debugging, archival, and integration testing
- Health Checks: Built-in health endpoints for monitoring and load balancers
- Hot Configuration Reload: Update settings without service restart
- Tag-based Filtering: Only notify for streams with specific tags (language, category, etc.)
- Custom Tag Injection: Add your own tags to webhook payloads for downstream processing
- Per-streamer Configuration: Different webhook URLs and settings for each streamer
This project includes a justfile
for easy development workflow management. Just is a command runner similar to make
but with a more modern syntax.
Install Just:
# macOS
brew install just
# Linux/macOS (using installer script)
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin
# Or download from releases: https://github.com/casey/just/releases
Common development commands:
# Show all available commands
just
# Build the application
just build
# Run with example config
just run
# Run tests
just test
# Run tests with coverage
just test-coverage
# Format code and run linter
just check
# Quick development cycle (format, test, build, run)
just dev
# Build for multiple platforms
just build-all
# Create config.toml from example
just config
Download the latest binary for your platform from the releases page:
Latest Release: v0.9.0
- Linux amd64:
itsjustintv-linux-amd64
- Linux arm64:
itsjustintv-linux-arm64
- macOS aarch64:
itsjustintv-darwin-aarch64
- Windows amd64:
itsjustintv-windows-amd64.exe
# Example for Linux
wget https://github.com/rmoriz/itsjustintv/releases/download/v0.3.0/itsjustintv-linux-amd64
chmod +x itsjustintv-linux-amd64
./itsjustintv-linux-amd64 --help
Or build from source:
git clone https://github.com/rmoriz/itsjustintv.git
cd itsjustintv
go build -o itsjustintv ./cmd/itsjustintv
-
Get Twitch Application Credentials
Create a Twitch application at dev.twitch.tv/console:
- Set OAuth Redirect URL to your webhook endpoint
- Note down your Client ID and Client Secret
- Generate a webhook secret for HMAC validation
-
Generate Configuration
./itsjustintv config example cp config.example.toml config.toml
-
Configure Your Streamers
Edit
config.toml
with your credentials and streamers:[twitch] client_id = "your_twitch_client_id" client_secret = "your_twitch_client_secret" webhook_secret = "your_webhook_secret" # Simple configuration - user_id will be resolved automatically! [streamers.my_favorite_streamer] login = "shroud" # Just the login name - user_id auto-resolved target_webhook_url = "https://your-webhook-endpoint.com/webhook" # Advanced configuration with filtering [streamers.another_streamer] login = "ninja" target_webhook_url = "https://another-endpoint.com/webhook" tag_filter = ["English", "Gaming"] # Only notify for these tags additional_tags = ["vip_streamer"] # Add custom tags to payload target_webhook_secret = "optional_hmac_secret" # Sign this webhook
-
Start the Service
./itsjustintv --config config.toml
The service will:
- Automatically resolve user IDs for streamers configured with just
login
- Start listening for Twitch webhooks on the configured port
- Begin dispatching notifications when streamers go live
- Automatically resolve user IDs for streamers configured with just
The service loads configuration files in the following priority order:
- Command line flag:
--config /path/to/config.toml
- Environment variable:
ITSJUSTINTV_CONFIG=/path/to/config.toml
- Default location:
config.toml
in the current directory
# Using command line flag
./itsjustintv --config /etc/itsjustintv/config.toml
# Using environment variable
export ITSJUSTINTV_CONFIG=/etc/itsjustintv/config.toml
./itsjustintv
# Using default location
./itsjustintv # Looks for config.toml in current directory
NEW: You can now configure streamers with just their login name! The service automatically resolves user IDs using Twitch's API during startup.
# Before (still supported)
[streamers.example_streamer]
user_id = "123456789"
login = "example_streamer"
target_webhook_url = "https://example.com/webhook"
# After (recommended - simpler!)
[streamers.example_streamer]
login = "example_streamer" # user_id will be auto-resolved
target_webhook_url = "https://example.com/webhook"
How it works:
- On startup, the service checks each streamer configuration
- If
user_id
is missing butlogin
is present, it queries Twitch's API - The resolved
user_id
is used internally (not saved to config file) - Logs show successful resolutions:
Resolved user ID for streamer 'example': login='shroud' -> user_id='37402112'
[server]
listen_addr = "0.0.0.0"
port = 8080
# External domain for reverse proxy scenarios (nginx, traefik, cloud load balancers)
external_domain = "your-domain.com"
# Optional HTTPS with Let's Encrypt
[server.tls]
enabled = true
domains = ["your-domain.com"]
cert_dir = "data/acme_certs"
[twitch]
client_id = "your_twitch_client_id"
client_secret = "your_twitch_client_secret"
webhook_secret = "your_webhook_secret_for_hmac_validation"
token_file = "data/tokens.json"
# Incoming webhook URL for Twitch EventSub subscriptions
# This is the URL Twitch will send webhook notifications to
# If not specified, it will be constructed from server configuration
incoming_webhook_url = "https://your-domain.com/twitch"
-
Create Twitch Application
- Go to Twitch Developer Console
- Click "Register Your Application"
- Set OAuth Redirect URL to
https://your-domain.com/twitch
- Note your Client ID and Client Secret
-
Generate Webhook Secret
- Create a random string for HMAC validation (e.g.,
openssl rand -hex 32
) - This validates that webhooks come from Twitch
- Create a random string for HMAC validation (e.g.,
[youtube]
api_key = "your_youtube_api_key"
webhook_secret = "your_youtube_webhook_secret"
websub_callback = "https://your-domain.com/youtube"
# Polling is disabled by default (WebSub is more efficient)
# Enable only if you need fallback polling for unreliable WebSub
# polling_enabled = true
# polling_interval = "5m"
# max_polling_retries = 3
quota_limit = 10000
quota_reserve = 500
retry_attempts = 3
websub_lease_seconds = 864000 # 10 days
-
Create Google Cloud Project
- Go to Google Cloud Console
- Create a new project or select existing one
-
Enable YouTube Data API v3
- Navigate to "APIs & Services" > "Library"
- Search for "YouTube Data API v3"
- Click "Enable"
-
Create API Key
- Go to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "API Key"
- Copy your API Key
- (Optional) Restrict the key to YouTube Data API v3
-
Configure WebSub Callback
- Set
websub_callback
tohttps://your-domain.com/youtube
- This is where YouTube will send real-time notifications
- Set
-
Generate Webhook Secret
- Create a random string for webhook validation (e.g.,
openssl rand -hex 32
)
- Create a random string for webhook validation (e.g.,
To monitor YouTube channels, you need their Channel IDs:
Method 1: From Channel URL
- Channel URL:
https://www.youtube.com/@channelname
- Go to channel page, view source, search for
"channelId":"UC..."
Method 2: Using YouTube API
curl "https://www.googleapis.com/youtube/v3/channels?part=id&forUsername=channelname&key=YOUR_API_KEY"
Method 3: Online Tools
- Use tools like commentpicker.com/youtube-channel-id.php
# Simple Twitch configuration (recommended)
[streamers.twitch_streamer]
login = "shroud" # user_id will be auto-resolved
target_webhook_url = "https://your-webhook-endpoint.com/webhook"
tag_filter = ["English", "Gaming"]
additional_tags = ["fps", "valorant"]
# Advanced Twitch configuration
[streamers.advanced_twitch]
user_id = "37402112" # Optional if login is provided
login = "shroud"
target_webhook_url = "https://your-webhook-endpoint.com/webhook"
target_webhook_secret = "optional_hmac_secret"
target_webhook_header = "X-Hub-Signature-256"
target_webhook_hashing = "SHA-256"
tag_filter = ["English", "Gaming"]
category_filter = ["Valorant", "Counter-Strike"]
additional_tags = ["pro_player", "fps"]
# YouTube configuration using Channel ID
[streamers.youtube_streamer]
youtube_channel_id = "UCrPseYLGpNygVi34QpGNqpA" # Ludwig's channel
target_webhook_url = "https://your-webhook-endpoint.com/webhook"
category_filter = ["Gaming", "Just Chatting"]
additional_tags = ["youtube", "variety"]
# YouTube configuration using Channel Handle (if supported)
[streamers.youtube_handle]
youtube_channel_handle = "@ludwig"
target_webhook_url = "https://your-webhook-endpoint.com/webhook"
tag_filter = ["Gaming", "Entertainment"]
additional_tags = ["youtube", "content-creator"]
# Multi-platform streamer (Twitch + YouTube)
[streamers.multi_platform]
login = "shroud" # Twitch
youtube_channel_handle = "@shroud" # YouTube
webhook_groups = ["all_platforms"]
additional_tags = ["multi-platform", "fps"]
[streamers.streamer_name]
# Option 1: Automatic resolution (recommended)
login = "streamer_login" # Twitch login name - user_id auto-resolved
# Option 2: Manual specification (still supported)
user_id = "123456789" # Twitch user ID
login = "streamer_login" # Twitch login name
# Webhook configuration - choose one or combine multiple approaches:
# Approach 1: Individual webhook URL (legacy)
target_webhook_url = "https://example.com/webhook"
# Approach 2: Use reusable webhook groups (recommended)
webhook_groups = ["discord_notifications", "slack_alerts"]
# Approach 3: Combine individual URL with groups
target_webhook_url = "https://example.com/webhook"
webhook_groups = ["discord_notifications"]
# Common settings
tag_filter = ["English", "Gaming"] # Optional: filter by stream tags
additional_tags = ["vip"] # Optional: add custom tags to payload
target_webhook_secret = "optional_secret" # Optional: HMAC sign this webhook
target_webhook_header = "X-Hub-Signature-256" # Optional: signature header name
target_webhook_hashing = "SHA-256" # Optional: hashing algorithm
Define reusable webhook configurations that can be assigned to multiple streamers:
[webhook_groups.discord_notifications]
name = "Discord Notifications"
enabled = true
target_webhook_url = "https://discord.com/api/webhooks/1234567890/abcdefg"
target_webhook_secret = "discord_webhook_secret"
[webhook_groups.slack_alerts]
name = "Slack Alerts"
enabled = true
target_webhook_url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
target_webhook_secret = "slack_webhook_secret"
[webhook_groups.custom_api]
name = "Custom API"
enabled = true
target_webhook_url = "https://your-api.example.com/webhook"
target_webhook_secret = "api_secret_key"
target_webhook_header = "X-API-Signature"
target_webhook_hashing = "SHA-256"
# Disabled webhook group (can be re-enabled without removing streamer references)
[webhook_groups.disabled_group]
name = "Disabled Group"
enabled = false
target_webhook_url = "https://disabled.example.com/webhook"
[retry]
max_attempts = 3
initial_delay = "1s"
max_delay = "5m"
backoff_factor = 2.0
state_file = "data/retry_state.json"
[output]
enabled = true
file_path = "data/output.json"
max_lines = 1000
[telemetry]
enabled = true
endpoint = "http://localhost:4318"
service_name = "itsjustintv"
service_version = "0.7.1"
For production deployments behind reverse proxies (nginx, traefik, cloud load balancers):
[server]
listen_addr = "127.0.0.1" # Bind to localhost
port = 8080 # Internal port
external_domain = "your-domain.com" # External domain for webhook URLs
[twitch]
# Optional: Use incoming_webhook_url for explicit control
incoming_webhook_url = "https://your-domain.com/twitch"
Priority order for webhook URLs:
twitch.incoming_webhook_url
(highest priority)server.external_domain
(for reverse proxy HTTPS)server.tls.domains[0]
(for direct HTTPS)server.listen_addr:port
(fallback)
Override any configuration with environment variables:
export ITSJUSTINTV_TWITCH_CLIENT_ID="your_client_id"
export ITSJUSTINTV_TWITCH_CLIENT_SECRET="your_client_secret"
export ITSJUSTINTV_TWITCH_WEBHOOK_SECRET="your_webhook_secret"
export ITSJUSTINTV_SERVER_PORT="8080"
export ITSJUSTINTV_TLS_ENABLED="true"
export ITSJUSTINTV_SERVER_EXTERNAL_DOMAIN="your-domain.com"
Webhook groups allow you to define reusable webhook configurations that can be assigned to multiple streamers, making configuration management much simpler when you have multiple streamers using the same notification channels.
- Define webhook groups in your configuration
- Assign groups to streamers using the
webhook_groups
array - Mix and match individual URLs and groups as needed
Simple webhook group usage:
[webhook_groups.discord]
target_webhook_url = "https://discord.com/api/webhooks/123/abc"
[webhook_groups.slack]
target_webhook_url = "https://hooks.slack.com/services/123/456/789"
[streamers.streamer1]
login = "streamer1"
webhook_groups = ["discord", "slack"]
Individual webhook with additional groups:
[streamers.streamer2]
login = "streamer2"
target_webhook_url = "https://example.com/special-webhook"
webhook_groups = ["discord"]
Migration from individual URLs: You can gradually migrate from individual webhook URLs to groups without breaking existing configurations.
When a streamer goes live, the service sends a rich JSON payload to the configured webhook URL:
{
"streamer_login": "shroud",
"streamer_name": "shroud",
"streamer_id": "37402112",
"url": "https://twitch.tv/shroud",
"view_count": 1337,
"followers_count": 50000,
"tags": ["English", "Gaming", "FPS"],
"language": "en",
"description": "Professional gamer and content creator",
"image": {
"url": "https://static-cdn.jtvnw.net/jtv_user_pictures/...",
"width": 300,
"height": 300
},
"timestamp": "2025-07-13T12:00:00Z",
"additional_tags": ["vip", "custom_tag"],
"stream": {
"id": "123456789",
"type": "live",
"started_at": "2025-07-13T12:00:00Z",
"title": "Playing some FPS games!",
"game_name": "Counter-Strike 2",
"game_id": "32399"
}
}
If you configure an hmac_secret
for a streamer, webhooks will include an HMAC signature in the X-Signature-256
header:
X-Signature-256: sha256=abc123def456...
Verify the signature in your webhook handler:
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# Start the server
./itsjustintv
# Start with specific config file
./itsjustintv --config /path/to/config.toml
# Enable verbose logging
./itsjustintv --verbose
# Show version information
./itsjustintv version
# Validate configuration
./itsjustintv config validate
# Generate example configuration
./itsjustintv config example [output_file]
# Show help
./itsjustintv --help
- Go 1.24.5 or later
- Git
- Just (recommended)
# Clone the repository
git clone https://github.com/rmoriz/itsjustintv.git
cd itsjustintv
# Install development tools
just install-tools
# Run the development cycle
just dev # formats, tests, builds, and runs
# Or run individual commands
just fmt # Format code
just lint # Run linter
just test # Run tests
just build # Build binary
just run # Run with example config
# Run all tests
just test
# Run tests with coverage
just test-coverage
# Run integration tests
just test-integration
# Watch for changes and re-run tests
just watch
# Build for current platform
just build
# Build with version information
just build-release v0.7.1
# Build for all platforms
just build-all v0.7.1
# Clean build artifacts
just clean
# Run all quality checks
just check
# Individual checks
just fmt # Format code
just lint # Run golangci-lint
just test # Run tests
# Pull the latest image
docker pull ghcr.io/rmoriz/itsjustintv:latest
# Run with Docker
docker run -d \
--name itsjustintv \
-p 8080:8080 \
-v $(pwd)/config.toml:/app/config.toml \
-v $(pwd)/data:/app/data \
ghcr.io/rmoriz/itsjustintv:latest
# Build Docker image
just docker-build itsjustintv:latest
# Run your custom image
just docker-run itsjustintv:latest 8080
version: '3.8'
services:
itsjustintv:
image: ghcr.io/rmoriz/itsjustintv:latest
ports:
- "8080:8080"
volumes:
- ./config.toml:/app/config.toml
- ./data:/app/data
environment:
- ITSJUSTINTV_TWITCH_CLIENT_ID=your_client_id
- ITSJUSTINTV_TWITCH_CLIENT_SECRET=your_client_secret
- ITSJUSTINTV_TWITCH_WEBHOOK_SECRET=your_webhook_secret
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
The service is built with a modular, production-ready architecture:
- HTTP Server: Handles incoming Twitch EventSub webhooks with graceful shutdown
- Config Manager: TOML configuration with environment variable overrides
- Twitch Client: Manages API interactions, token lifecycle, and user resolution
- Webhook Dispatcher: Concurrent notification delivery with retry logic
- Metadata Enricher: Fetches and caches streamer metadata with fallbacks
- Retry Manager: Persistent retry queue with exponential backoff
- Cache Manager: Event deduplication and metadata caching
- Output Writer: Structured JSON logging for debugging and integration
- Startup: Load config → Resolve user IDs → Start Twitch client → Initialize services
- Webhook Receipt: Validate signature → Process notification → Check for duplicates
- Event Processing: Find streamer config → Enrich metadata → Create payload
- Delivery: Dispatch webhook → Handle failures → Queue retries → Log results
- HMAC signature validation for incoming webhooks
- Optional HMAC signing for outgoing webhooks
- Let's Encrypt integration for HTTPS
- No sensitive data in logs
- Secure token storage and rotation
For detailed architecture information, see docs/architecture.md.
# Basic health check
curl http://localhost:8080/health
# Response
{
"status": "healthy",
"service": "itsjustintv",
"timestamp": "2025-07-13T12:00:00Z"
}
Enable comprehensive observability:
[telemetry]
enabled = true
endpoint = "http://localhost:4318" # OTLP HTTP endpoint
service_name = "itsjustintv"
service_version = "0.7.1"
Metrics collected:
- Webhook processing latency
- Success/failure rates
- Retry queue depth
- API call performance
Traces include:
- End-to-end webhook processing
- Twitch API interactions
- Metadata enrichment
- Webhook delivery attempts
Structured JSON logs with configurable levels:
# Enable verbose logging
./itsjustintv --verbose
# Example log output
{"time":"2025-07-13T12:00:00Z","level":"INFO","msg":"Resolved user ID for streamer","streamer_key":"shroud","login":"shroud","user_id":"37402112"}
{"time":"2025-07-13T12:00:00Z","level":"INFO","msg":"Webhook dispatched successfully","webhook_url":"https://example.com/webhook","streamer_key":"shroud","response_time":"150ms"}
User ID Resolution Fails
# Check Twitch credentials
./itsjustintv config validate
# Verify streamer login exists
curl -H "Client-ID: your_client_id" \
-H "Authorization: Bearer your_token" \
"https://api.twitch.tv/helix/users?login=streamer_name"
Webhooks Not Received
- Verify EventSub subscription is active in Twitch Developer Console
- Check webhook URL is publicly accessible
- Validate HMAC signature implementation
- Review server logs for signature validation errors
High Memory Usage
- Reduce cache retention period
- Lower
max_lines
in output configuration - Check for webhook endpoint timeouts causing retry buildup
# Enable verbose logging
./itsjustintv --verbose
# Check configuration
./itsjustintv config validate
# Test webhook endpoint
curl -X POST your-webhook-url \
-H "Content-Type: application/json" \
-d '{"test": "payload"}'
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/itsjustintv.git
- Create a feature branch:
git checkout -b feature/amazing-feature
- Install development tools:
just install-tools
- Make your changes and test:
just check
- Commit your changes:
git commit -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
- Follow Go best practices and idioms
- Add tests for new functionality
- Update documentation for user-facing changes
- Run
just check
before committing - Use conventional commit messages
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: docs/
- Examples: examples/
See CHANGELOG.md for a detailed history of changes.
itsjustintv - Making Twitch stream notifications simple, reliable, and production-ready.