Connect to any TikTok Live stream and receive real-time events in Go. No signing server, no API keys, no authentication required.
package main
import (
"context"
"fmt"
golive "github.com/PirateTok/live-go"
"github.com/PirateTok/live-go/events"
pb "github.com/PirateTok/live-go/proto"
)
func main() {
// Connect returns a channel of decoded events
ch, err := golive.NewClient("username_here").Connect(context.Background())
if err != nil {
panic(err)
}
// Events arrive as fully decoded protobuf messages
for evt := range ch {
switch evt.Type {
case events.EventConnected:
fmt.Println("connected to room:", evt.RoomID)
case events.EventChat:
msg := evt.Data.(*pb.WebcastChatMessage)
fmt.Printf("[chat] %s: %s\n", msg.User.Nickname, msg.Content)
case events.EventGift:
msg := evt.Data.(*pb.WebcastGiftMessage)
giftName := fmt.Sprintf("gift#%d", msg.GiftId)
if msg.Gift != nil {
giftName = msg.Gift.Name
}
fmt.Printf("[gift] %s sent %s x%d\n",
msg.User.Nickname, giftName, msg.RepeatCount)
case events.EventDisconnected:
fmt.Println("disconnected")
return
}
}
}go get github.com/PirateTok/live-go
| Language | Install | Repo |
|---|---|---|
| Rust | cargo add piratetok-live-rs |
live-rs |
| Python | pip install piratetok-live-py |
live-py |
| JavaScript | npm install piratetok-live-js |
live-js |
| C# | dotnet add package PirateTok.Live |
live-cs |
| Java | com.piratetok:live |
live-java |
| Lua | luarocks install piratetok-live-lua |
live-lua |
| Elixir | {:piratetok_live, "~> 0.1"} |
live-ex |
| Dart | dart pub add piratetok_live |
live-dart |
| C | #include "piratetok.h" |
live-c |
| PowerShell | Install-Module PirateTok.Live |
live-ps1 |
| Shell | bpkg install PirateTok/live-sh |
live-sh |
- Zero signing dependency — no API keys, no signing server, no external auth
- 64 decoded event types — committed protobuf codegen, no build-time protoc
- Auto-reconnection — stale detection, exponential backoff, self-healing auth
- Full proxy support — HTTP/HTTPS/SOCKS5 proxy for all HTTP and WSS connections, env var fallback
- Enriched User data — badges, gifter level, moderator status, follow info, fan club
- Sub-routed convenience events —
EventFollow,EventShare,EventJoin,EventLiveEnded - 2 dependencies —
gobwas/ws+google.golang.org/protobuf
client := golive.NewClient("username_here").
CdnEU().
MaxRetries(10).
StaleTimeout(90 * time.Second)| Method | Default | Description |
|---|---|---|
.CdnEU() |
global | Use the EU CDN endpoint |
.CdnUS() |
global | Use the US CDN endpoint |
.Cdn(host) |
"webcast-ws.tiktok.com" |
Custom CDN host |
.Timeout(d) |
10s |
HTTP timeout for API calls |
.HeartbeatInterval(d) |
10s |
Interval between WSS heartbeat frames (also sent as heartbeat_duration) |
.MaxRetries(n) |
5 |
Max consecutive failed attempts (reset after a 30s healthy session) |
.StaleTimeout(d) |
60s |
Close and reconnect after no data for this duration |
.UserAgent(ua) |
random pool | Override the random UA pool with a fixed UA |
.Cookies(cookies) |
none | Append session cookies alongside ttwid in the WSS cookie header |
.Language(lang) |
system locale | Override detected language (e.g. "pt", "ro") |
.Region(reg) |
system locale | Override detected region (e.g. "BR", "RO") |
.Proxy(url) |
env vars | HTTP/HTTPS/SOCKS5 proxy URL for all HTTP and WSS connections |
.Compress(enabled) |
true |
Disable gzip compression for WSS payloads (trades bandwidth for CPU) |
info, err := golive.FetchRoomInfo("ROOM_ID", 10*time.Second, "")
// 18+ rooms
info, err := golive.FetchRoomInfo("ROOM_ID", 10*time.Second, "sessionid=abc; sid_tt=abc")Every EventRoomUserSeq carries the counters and the top-viewers box — no cookies needed:
msg := evt.Data.(*pb.WebcastRoomUserSeqMessage)
msg.ViewerCount // in the room right now (goes up and down)
msg.TotalUser // unique viewers over the whole stream (only grows)
for _, c := range msg.TopViewers() { // usually top 3, sorted by rank
fmt.Println(c.Rank, c.GetUser().GetNickname(), c.Score)
}The full audience roster is a separate call. TikTok gates it behind a login, so session cookies are
required for this call only — without them it returns *tthttp.SessionRequiredError:
room, _ := golive.CheckOnline("username_here", 10*time.Second)
audience, err := golive.FetchRoomAudience(room.RoomID, room.AnchorID, 10*time.Second, "sessionid=abc; sid_tt=abc")
// audience.Total, audience.Anonymous, audience.Viewers (rank, score, username, follower count, ...)Pass "" as the anchor ID to resolve it from room info (one extra request).
Connect returns once the room is resolved; the reconnect loop runs in the background until the
context is cancelled or MaxRetries consecutive attempts fail. The ttwid cookie is fetched with up to
8 retries (TikTok only sets it intermittently), then reused across reconnects together with the UA —
both rotate only on DEVICE_BLOCKED or a connection that died within 30s.
go run ./cmd/basic_chat <username> # connect + print chat events
go run ./cmd/online_check <username> # check if user is live
go run ./cmd/stream_info <username> # fetch room metadata + stream URLs
go run ./cmd/gift_tracker <username> # track gifts with diamond totals
go run ./cmd/gift_streak <username> # track gift streaks with delta computation
go run ./cmd/profile_lookup [username] # fetch profile via SIGI scraper + cache
go run ./cmd/audience <username> "sessionid=...; sid_tt=..." # full viewer roster (login required)Deterministic cross-lib validation against binary WSS captures. Requires testdata from a separate repo:
git clone https://github.com/PirateTok/live-testdata testdata
go test -run TestReplay -vReplay tests fail if testdata is not found — they never pass on missing data. Set PIRATETOK_TESTDATA to point to a custom location.
Offline unit tests (ttwid retry, reconnect budget, top viewers, audience parsing) need no network: go test ./....
0BSD
