Overview
EventSub WebSocket provides real-time Twitch events over a persistent WebSocket connection. Unlike webhooks, no public HTTPS endpoint is required.
Advantages over Webhooks:
- No public endpoint needed - works behind firewalls/NAT
- Lower latency - direct connection, no HTTP overhead
- Simpler setup - no SSL certificates required
- Ideal for bots, overlays, and local applications
How it works:
- Connect to Twitch’s WebSocket endpoint
- Receive a session ID in the welcome message
- Create subscriptions using the session ID
- Receive events on the WebSocket connection
- Handle keepalives and reconnection messages
Connection limits: Each WebSocket session can have up to 300 subscriptions. For more, use multiple connections.
Low-Level Client
Full control over WebSocket connection and event handling. Use this when you need direct access to connection lifecycle events or custom subscription management.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/Its-donkey/helix/helix"
)
func main() {
ctx := context.Background()
// Create auth and helix clients
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
_, _ = authClient.GetAppAccessToken(ctx)
helixClient := helix.NewClient("your-client-id", authClient)
// Create WebSocket client
wsClient := helix.NewEventSubWebSocketClient(
helix.WithWSNotificationHandler(func(sub *helix.EventSubSubscription, event json.RawMessage) {
switch sub.Type {
case helix.EventSubTypeStreamOnline:
e, _ := helix.ParseWSEvent[helix.StreamOnlineEvent](event)
fmt.Printf("Stream online: %s\n", e.BroadcasterUserName)
case helix.EventSubTypeStreamOffline:
e, _ := helix.ParseWSEvent[helix.StreamOfflineEvent](event)
fmt.Printf("Stream offline: %s\n", e.BroadcasterUserName)
case helix.EventSubTypeChannelFollow:
e, _ := helix.ParseWSEvent[helix.ChannelFollowEvent](event)
fmt.Printf("New follower: %s\n", e.UserName)
}
}),
helix.WithWSReconnectHandler(func(reconnectURL string) {
log.Printf("Reconnecting to: %s", reconnectURL)
wsClient.Reconnect(ctx, reconnectURL)
}),
helix.WithWSErrorHandler(func(err error) {
log.Printf("WebSocket error: %v", err)
}),
)
// Connect and get session ID
sessionID, err := wsClient.Connect(ctx)
if err != nil {
log.Fatal(err)
}
defer wsClient.Close()
fmt.Printf("Connected with session ID: %s\n", sessionID)
// Create EventSub subscriptions using the session ID
_, err = helixClient.CreateEventSubSubscription(ctx, &helix.CreateEventSubSubscriptionParams{
Type: helix.EventSubTypeStreamOnline,
Version: "1",
Condition: map[string]string{
"broadcaster_user_id": "12345",
},
Transport: helix.CreateEventSubTransport{
Method: "websocket",
SessionID: sessionID,
},
})
if err != nil {
log.Fatal(err)
}
// Keep running to receive events
select {}
}
High-Level Wrapper
Simplified interface that manages subscriptions automatically. The Subscribe method handles both creating the EventSub subscription and registering the event handler in one call.
Recommended for most use cases - handles session management, subscription creation, and event routing internally.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/Its-donkey/helix/helix"
)
func main() {
ctx := context.Background()
// Create auth and helix clients
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
_, _ = authClient.GetAppAccessToken(ctx)
helixClient := helix.NewClient("your-client-id", authClient)
// Create high-level WebSocket wrapper
ws := helix.NewEventSubWebSocket(helixClient)
if err := ws.Connect(ctx); err != nil {
log.Fatal(err)
}
defer ws.Close()
// Subscribe to events with automatic handler registration
ws.Subscribe(ctx, helix.EventSubTypeStreamOnline, "1",
map[string]string{"broadcaster_user_id": "12345"},
func(event json.RawMessage) {
e, _ := helix.ParseWSEvent[helix.StreamOnlineEvent](event)
fmt.Printf("Stream online: %s\n", e.BroadcasterUserName)
},
)
ws.Subscribe(ctx, helix.EventSubTypeChannelFollow, "2",
map[string]string{
"broadcaster_user_id": "12345",
"moderator_user_id": "12345",
},
func(event json.RawMessage) {
e, _ := helix.ParseWSEvent[helix.ChannelFollowEvent](event)
fmt.Printf("New follower: %s\n", e.UserName)
},
)
// Keep running to receive events
select {}
}
Handling Reconnection
Twitch sends a reconnect message when the server needs to migrate your connection (e.g., before maintenance). You have 30 seconds to reconnect to the new URL.
Important: Your subscriptions are preserved - just reconnect to the new URL and continue receiving events.
wsClient := helix.NewEventSubWebSocketClient(
helix.WithWSNotificationHandler(handleNotification),
helix.WithWSReconnectHandler(func(reconnectURL string) {
log.Printf("Twitch requested reconnection")
// Reconnect to the new URL
if err := wsClient.Reconnect(ctx, reconnectURL); err != nil {
log.Printf("Reconnection failed: %v", err)
// Implement retry logic or fallback
}
}),
helix.WithWSKeepaliveHandler(func() {
log.Printf("Keepalive received")
}),
)
Error Handling
Handle connection errors and implement automatic reconnection. Common errors include network issues, connection timeouts, and server-side disconnects.
Best practice: Implement exponential backoff when reconnecting to avoid overwhelming the server.
wsClient := helix.NewEventSubWebSocketClient(
helix.WithWSNotificationHandler(handleNotification),
helix.WithWSErrorHandler(func(err error) {
log.Printf("WebSocket error: %v", err)
// Implement reconnection logic
go func() {
time.Sleep(5 * time.Second)
if _, err := wsClient.Connect(ctx); err != nil {
log.Printf("Reconnection failed: %v", err)
}
}()
}),
)
Note: Expected close errors (like “use of closed network connection” during shutdown) are automatically filtered and won’t trigger the error handler.
Supported Event Types
See EventSub documentation for a complete list of event types.