Migrating from Twitch PubSub
Note: Twitch PubSub was fully decommissioned on April 14, 2025. Kappopher provides a compatibility layer that uses EventSub under the hood.
Using the PubSub Compatibility Layer
If you want to minimize code changes, use our PubSub compatibility layer:
// Old PubSub code (no longer works)
pubsub.Listen("channel-points-channel-v1.12345", token)
// New Kappopher code (uses EventSub internally)
pubsub := helix.NewPubSubClient(helixClient,
helix.WithPubSubMessageHandler(func(topic string, message json.RawMessage) {
// Handle messages
}),
)
pubsub.Connect(ctx)
pubsub.Listen(ctx, "channel-points-channel-v1.12345")
Supported Topic Mappings
| PubSub Topic | EventSub Type(s) |
|---|---|
channel-bits-events-v1.<channel_id> |
channel.cheer |
channel-bits-events-v2.<channel_id> |
channel.cheer |
channel-points-channel-v1.<channel_id> |
channel.channel_points_custom_reward_redemption.add |
channel-subscribe-events-v1.<channel_id> |
channel.subscribe, channel.subscription.gift, channel.subscription.message |
chat_moderator_actions.<user_id>.<channel_id> |
channel.moderate |
whispers.<user_id> |
user.whisper.message |
Key Differences
- Authentication - Requires a Helix client for creating subscriptions
- Context - All operations use
context.Context - Message format - Events use EventSub format, not old PubSub format
- Connection - Must call
Connect()beforeListen()
Migrating Directly to EventSub
For new code or major refactors, we recommend using EventSub directly:
// EventSub WebSocket (recommended for most use cases)
es := helix.NewEventSubClient(helixClient,
helix.WithEventSubMessageHandler(func(event helix.EventSubMessage) {
switch e := event.Event.(type) {
case *helix.ChannelPointsRedemptionAddEvent:
fmt.Printf("%s redeemed %s\n", e.UserName, e.Reward.Title)
}
}),
)
es.Connect(ctx)
es.SubscribeToChannelPointsRedemption(ctx, broadcasterID)
See EventSub documentation for full details.
Migrating from go-twitch-irc
If you’re using gempir/go-twitch-irc, here’s how to migrate:
Basic Connection
go-twitch-irc:
client := twitch.NewClient("username", "oauth:token")
client.OnPrivateMessage(func(message twitch.PrivateMessage) {
fmt.Println(message.Message)
})
client.Join("channel")
client.Connect()
Kappopher:
irc := helix.NewIRCClient(
helix.WithIRCNick("username"),
helix.WithIRCToken("oauth:token"),
helix.WithIRCMessageHandler(func(msg *helix.IRCMessage) {
if msg.Command == "PRIVMSG" {
fmt.Println(msg.Text)
}
}),
)
irc.Connect(ctx)
irc.Join("channel")
Message Structure
go-twitch-irc:
message.User.DisplayName
message.User.ID
message.Message
message.Channel
message.Tags["emotes"]
Kappopher:
msg.Tags["display-name"]
msg.Tags["user-id"]
msg.Text
msg.Channel
msg.Tags["emotes"]
Sending Messages
go-twitch-irc:
client.Say("channel", "Hello!")
client.Reply("channel", "parent-msg-id", "Reply text")
Kappopher:
irc.Say("channel", "Hello!")
irc.Reply("channel", "parent-msg-id", "Reply text")
Event Handlers
| go-twitch-irc | Kappopher |
|---|---|
OnPrivateMessage |
Check msg.Command == "PRIVMSG" |
OnUserNoticeMessage |
Check msg.Command == "USERNOTICE" |
OnClearChatMessage |
Check msg.Command == "CLEARCHAT" |
OnClearMessage |
Check msg.Command == "CLEARMSG" |
OnRoomStateMessage |
Check msg.Command == "ROOMSTATE" |
OnConnect |
WithIRCConnectHandler |
OnReconnect |
WithIRCReconnectHandler |
Migrating from nicklaw5/helix
If you’re using nicklaw5/helix, here’s how to migrate:
Client Setup
nicklaw5/helix:
client, _ := helix.NewClient(&helix.Options{
ClientID: "client-id",
ClientSecret: "client-secret",
AppAccessToken: "token",
})
Kappopher:
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "client-id",
ClientSecret: "client-secret",
})
token, _ := authClient.GetAppAccessToken(ctx)
authClient.SetToken(token)
client := helix.NewClient("client-id", authClient)
API Calls
nicklaw5/helix:
resp, err := client.GetUsers(&helix.UsersParams{
Logins: []string{"shroud"},
})
user := resp.Data.Users[0]
Kappopher:
resp, err := client.GetUsers(ctx, &helix.GetUsersParams{
Logins: []string{"shroud"},
})
user := resp.Data[0]
Key Differences
- Context required - All API methods require
context.Context - Separate auth client - Authentication is handled by
AuthClient - Response structure -
resp.Datais the slice directly, not wrapped - Generics - Kappopher uses Go generics for type safety
Error Handling
nicklaw5/helix:
if resp.ErrorMessage != "" {
// Handle error
}
Kappopher:
if err != nil {
if apiErr, ok := err.(*helix.APIError); ok {
fmt.Printf("Status %d: %s\n", apiErr.StatusCode, apiErr.Message)
}
}
Migrating from Direct API Calls
If you’re making direct HTTP calls to the Twitch API:
Before (raw HTTP)
req, _ := http.NewRequest("GET", "https://api.twitch.tv/helix/users?login=shroud", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Client-Id", clientID)
resp, _ := http.DefaultClient.Do(req)
// Parse JSON manually...
After (Kappopher)
resp, err := client.GetUsers(ctx, &helix.GetUsersParams{
Logins: []string{"shroud"},
})
if err != nil {
return err
}
user := resp.Data[0]
Benefits
- Type safety - Responses are properly typed structs
- Auto-retry - Handles rate limits and transient failures
- Token refresh - Automatic token management
- Pagination - Built-in cursor handling
- Caching - Optional response caching
- Middleware - Extensible request/response processing
Feature Comparison
| Feature | Kappopher | nicklaw5/helix | go-twitch-irc |
|---|---|---|---|
| Helix API | 147+ endpoints | ~100 endpoints | - |
| IRC Chat | Yes | No | Yes |
| EventSub WebSocket | Yes | No | No |
| EventSub Webhooks | Yes | Partial | No |
| PubSub Compat | Yes | No | No |
| Auto Token Refresh | Yes | Manual | - |
| Caching | Yes | No | No |
| Batch Operations | Yes | No | No |
| Middleware | Yes | No | No |
| Go Generics | Yes | No | No |