kicksdk

Kick: what we know

Everything learnt about Kick as a data source, across KickTracker, BetterChat, KickPlus and Viewmux: the official API and webhooks, Kick's own website endpoints, and the realtime chat feed. kicksdk is built on it, and this is its canonical copy: when Kick behaves differently from what is written here, correct it here first, then the fixtures, then the code (see CONTRIBUTING.md).

Each fact says where it comes from: Kick's docs; a recording (anonymized into fixtures/, by KickTracker's recorder or tools/record-realtime.mjs); a probe (one request per endpoint); KickTracker's sandbox in real-Kick mode; production (what KickTracker's collector has received since 2026-09-24); or a client (what BetterChat and KickPlus do against the live site, and what Kick's own frontend bundle does).

"We" and "our" in §1–4 are KickTracker, where most of those facts were learnt; its use is kept beside each one because the reason for a choice is part of the fact (polling every 60 s because the count changes once a minute).

Last updated 2026-10-13.


1. The sources at a glance

Source Status Auth What only it gives Our use
Public API api.kick.com/public/v1 Official, documented App token Viewer counts, channel state, subscriber totals Polled: /livestreams every 60s, /channels every 5 min
Webhooks Official, documented App token subscribes; Kick signs deliveries Follows, subs, gifts, Kicks as they happen; stream start/end, metadata 7 event types per channel, through our ingress
v2 website API kick.com/api/v2 Undocumented, Kick's own frontend None Follower total, chatroom id One channel per request: every 15 min live, daily offline
Other website endpoints Undocumented None (mostly) Past streams, gift leaderboards, clips Probed only; nothing uses them
Realtime feed (Centrifugo, *.platform.kick.com) Unofficial (what kick.com's chat uses) A guest token, no account Chat, hosts (raids), moderation events, Kicks, live/offline One socket per chat; Origin must be kick.com (§6)

Nothing gives: viewer history before we started tracking, a livestream id in the official API, unfollows, audience demographics, or personal facts.

2. Authorization

3. Public API (https://api.kick.com/public/v1)

Endpoints we use

Endpoint Gives Batching
GET /livestreams?broadcaster_user_id=… broadcaster_user_id, channel_id, slug, viewer_count, started_at, stream_title, category {id, name, thumbnail}, language, has_mature_content, thumbnail, profile_picture (the docs also list tags; none in the recorded answers) 50 ids per request (the parameter repeated)
GET /channels?broadcaster_user_id=… or ?slug=… slug, broadcaster id, title, category, stream (is_live, viewer_count, start, language, tags), description, banner, active_subscribers_count, active_gifted_subscribers_count, canceled_subscribers_count 50 per request, by id or by slug, not mixed
GET /public-key The PEM key webhooks are signed with –
GET/POST/DELETE /events/subscriptions Webhook subscriptions (§4.2) –

/livestreams also filters by category_id and language, sorts by viewer_count and takes limit up to 100: usable for category rankings. (docs)

Behaviour observed

4. Webhooks

4.1 Event types

The ten types Kick documents, with what production has received (2026-09-24 → 26, 47 channels):

Event Version Received Body (fields seen) Our use
livestream.status.updated 1 110 broadcaster, is_live, title, started_at, ended_at Stream start and end
livestream.metadata.updated 1 139 broadcaster, metadata {title, language, has_mature_content, category and Category} Title/category changes
channel.followed 1 3 457 broadcaster, follower Gross follows
channel.subscription.new 1 31 broadcaster, subscriber, duration, created_at, expires_at New subs
channel.subscription.renewal 1 14 same as new Resubs
channel.subscription.gifts 1 222 broadcaster, gifter, giftees[], created_at, expires_at Gifted subs
kicks.gifted 1 367 broadcaster, sender, gift {amount, name, type, tier, message, pinned_time_seconds}, created_at Kicks
moderation.banned – not subscribed bans and timeouts Chat feed has richer moderation (§6.4)
channel.reward.redemption.updated – not subscribed channel-point redemptions –
chat.message.sent – not subscribed every chat message Chat comes from Pusher

Every type we subscribe to has now been delivered with the app token for channels that never authorized us. Only status, metadata and follows are recorded in fixtures/; the sub, gift and Kicks shapes above come from production's stored bodies (key names only) and should be recorded and anonymized into fixtures before a parser change relies on a new field.

A person in any body (broadcaster, follower, subscriber, gifter, giftees[], sender) is {user_id, username, channel_slug, is_verified, is_anonymous, profile_picture, identity} (identity null so far; kicks.gifted's broadcaster has neither is_anonymous nor identity).

Details worth knowing:

4.2 Subscriptions

4.3 Delivery

5. Kick's website endpoints (undocumented)

Kick's own frontend API. Can change or be blocked at any time and is a grey area under Kick's terms: everything using it is isolated, optional, and a failure is a gap, never a zero.

5.1 v2 channel: GET https://kick.com/api/v2/channels/<slug>

5.2 Other endpoints probed (2026-09-24, from a home machine)

Endpoint Result Possible use
api.kick.com/private/v1/channels/{slug} 200, no auth Fallback follower source: its count was 0.06% higher than v2's at almost the same moment, and it uses opaque string ids (channel_…, user_…). A channel's follower history must come from one source only
api.kick.com/private/v1/livestreams 200: all live streams by viewers, 20 per page, cursor Rankings (the official /livestreams is preferred)
kick.com/current-viewers?ids[]= 200: [{livestream_id, viewers, show_view_count}] Several streams' viewers in one call
kick.com/api/v2/channels/{slug}/videos 200: the last ~27 days of streams; duration in milliseconds; viewer_count 0 for every finished stream Airtime and category history from before tracking
kick.com/api/v1/channels/{slug} 200 (51 KB): the same past streams, followersCount equal to v2's Alternative to the above
kick.com/api/v2/channels/{slug}/leaderboards 200: top gifters all time (10), month (10), week (5) Partial gift history; a cross-check
kick.com/api/v2/channels/{slug}/clips 200: clips with view_count, likes_count, cursor A later clips feature
kick.com/api/v2/channels/{slug}/livestream 200: live stream details, viewers –
kick.com/api/v2/channels/{slug}/chatroom 200: chat settings (followers-only, slow mode…) Context for chat activity
kick.com/api/v2/channels/{slug}/subscribers/last 401 (needs a login) –
api.kick.com/channels/:id/followers-count, api.kick.com/private/v0/channels/:id/viewer-count, …/videos/latest, private/v1/channels/{slug}/clips 404 Gone

Source for candidates: the community list fb-sean/kick-website-endpoints.

5.3 Reading and acting from a client

What BetterChat and KickPlus use against the live site. (client, 2026-10)

Endpoint Auth Gives / does
GET kick.com/api/v2/channels/<channel id>/messages None Recent chat (data.messages, newest first) and data.pinned_message (the pin event's shape plus finish_at). Note: by channel id, not slug
GET kick.com/api/v2/channels/<slug>/users/<username> None A user as the channel sees them: following_since, subscribed_for, is_moderator, banned, badges. 404 when the channel does not know the name (BetterChat uses it to tell @user from @word)
GET kick.com/api/v2/channels/<slug>/me Session is_moderator, is_broadcaster, is_super_admin; 401/403 signed out
POST kick.com/api/v2/channels/<slug>/bans Session Ban {permanent: true, banned_username, reason: null}, timeout {permanent: false, banned_username, reason: null, duration: <minutes>}
DELETE kick.com/api/v2/channels/<slug>/bans/<username> Session Lift a ban or timeout
DELETE kick.com/api/v2/chatrooms/<chatroom id>/messages/<message id> Session Delete a message (needs the chatroom id)
POST kick.com/api/v2/channels/<slug>/pinned-message Session Pin {message: <the whole message as the feed sent it>, duration: <minutes>}: Kick hands the message back to every viewer as the banner
DELETE kick.com/api/v2/channels/<slug>/pinned-message Session Unpin (there is only ever one)
POST kick.com/api/v2/messages/send/<chatroom id> Session A reply: {content, type: "reply", message_ref, metadata: {original_message: {id, content}, original_sender: {id, username}}, thread_parent_id}. message_ref is any string (Kick's page sends the clock); the feed echoes it back

6. Realtime chat feed

6.1 Centrifugo (since October 2026)

Kick moved its realtime feed from Pusher to Centrifugo. The Pusher app key (32cbd69e4b950bf97679, cluster us2) stopped working: every cluster answers {"event":"pusher:error","data":{"code":4001,"message":"App key 32cbd69e4b950bf97679 not in this cluster…"}} and closes. Noticed 2026-10-13 when BetterChat, KickPlus and KickTracker all lost chat at once. (client)

Finding the server (client: Kick's frontend bundle, then live)

The token

Origin and CORS (client)

Protocol (Centrifugo JSON protocol; server "version": "6.9.7 PRO")

Event What the driver calls
The session says close close the socket, session.closed(), then wait backoff.next_delay_for(close.retry_after_ms, random); if close.terminal, make a new client id
The socket closes by itself with a code session.closed(), then wait backoff.next_delay_for(realtime::retry_after_ms(code), random); more than 0 is terminal: new client id
The socket fails without a code, a request fails wait backoff.next_delay_for(0, random) (the ordinary 1 s → 30 s)
subscribed for the chat feed (the first in chat_channels) backoff.reset()

A driver stops only when its host asks it to. - No ping in the connect reply means the server will not ping (a Centrifugo server can be set up so); the 25 s silence rule then does not apply, and kicksdk does not close a quiet feed. Kick has always sent ping: 25 so far. - No validation of ids: subscribing to chatrooms.999999999.v2 (no such chatroom) succeeds, and nothing ever arrives.

The feeds for one chat (Kick's page joins all five) (client: Kick's bundle, BetterChat; recording)

Feed Carries
chatrooms.<chatroom id>.v2 Chat, deletions, bans, subs, clears, pins, polls, hosts arriving (StreamHostEvent)
chatrooms.<chatroom id> The same hosts again, as StreamHostedEvent (another shape: message {numberOfViewers, optionalMessage}, a whole user). Counting both counts every host twice
chatroom_<chatroom id> Gifted subs (GiftedSubscriptionsEvent, unprefixed), reward redemptions (RewardRedeemedEvent, unprefixed)
channel.<channel id> Live / offline (StreamerIsLive, StopStreamBroadcast), hosts leaving (ChatMoveToSupportedChannelEvent)
channel_<channel id> Kicks (KicksGifted, unprefixed)

Subscribing to .v2 alone was why gifted subs never reached BetterChat for a while: the handler was right, and nothing was ever delivered to it.

Limits: subscriptions per connection and connections per IP are unknown. BetterChat's relay holds one connection per chatroom from one VPS.

6.1a Pusher (until October 2026, kept for the record)

6.2 Chat messages: App\Events\ChatMessageEvent

Fields: id (UUID), chatroom_id, content, type (message or reply), created_at, sender {id, username, slug, identity {color, badges, badges_v2}}, metadata {message_ref, and on a reply original_message {id, content}, original_sender {id, username}}, thread_parent_id on replies. (recording)

6.3 Hosts (Kick's raids)

Kick calls raids hosts. One host shows on both sides, under a second apart when both channels are tracked; neither event carries a time. (production, sandbox; fixtures: fixtures/pusher/20260926T203429Z-pusher__*.jsonl)

Event Feed Fields
App\Events\StreamHostEvent the receiving channel's chatrooms.<id>.v2 chatroom_id, host_username, number_viewers, optional_message (free text, may be null or "")
App\Events\ChatMoveToSupportedChannelEvent the hosting channel's channel.<id> slug (the hosted channel), hosted {id (a channel id), slug, username, viewers_count, is_live, category, profile_pic, preview_thumbnail}, and the whole hosting channel (with its playback_url and current_livestream)

6.4 Moderation and other events

Seen on logged channels' chatrooms in production (one day, two channels), and by BetterChat on the live feed:

Event Count Fields
App\Events\UserBannedEvent 665 id, user {id, slug, username}, banned_by {id, slug, username}, permanent, duration (timeouts only, minutes: 1 to 5 seen), expires_at
App\Events\UserUnbannedEvent 3 id, user, unbanned_by, permanent
App\Events\MessageDeletedEvent 19 id, message {id}, aiModerated, violatedRules[] (17 of 19 were AI-moderated, each with rules listed)
App\Events\PinnedMessageCreatedEvent 10 message (a full chat message), pinnedBy, duration (a string, "1200" every time: seconds, presumably)
App\Events\PinnedMessageDeletedEvent 2 data is an empty array
App\Events\StopStreamBroadcast 1 livestream {id, channel}

| App\Events\ChatroomClearEvent | – | (nothing needed) | | App\Events\SubscriptionEvent | – | username, months | | GiftedSubscriptionsEvent (on chatroom_<id>; App\Events\… also accepted) | – | gifter_username, gifted_usernames[] | | KicksGifted (on channel_<id>) | – | gift_transaction_id, message (beside gift, not inside it; only the larger gifts carry one), sender {id, username, username_color, profile_picture}, gift {gift_id, name, amount, type, tier, character_limit, pinned_time}, created_at | | RewardRedeemedEvent (on chatroom_<id>) | – | Shape has varied: title as reward_title or reward.title, typed input as user_input; read both | | App\Events\StreamerIsLive (on channel.<id>) | – | livestream {id, session_title, …} | | App\Events\PollUpdateEvent / PollDeleteEvent | – | poll {…, remaining, result_display_duration} |

Permanent bans far outnumber timeouts (659 against 6). Deleted messages name only the message id: the text has to come from the chat log. (the rows without counts: client, BetterChat)

7. Identities, names and images

8. Open questions

9. Where this lives in kicksdk

What Where
Ids kept apart (user, channel, chatroom) crates/kicksdk/src/ids.rs
Public API, app token, public key, subscriptions crates/kicksdk/src/public_api.rs
Website API (§5) crates/kicksdk/src/website.rs
Realtime endpoints and the Centrifugo session (§6.1) crates/kicksdk/src/realtime/
Chat-feed events (§6.2–6.4) crates/kicksdk/src/events.rs
Emotes in message text crates/kicksdk/src/content.rs
Webhook headers, signature, bodies (§4) crates/kicksdk/src/webhooks.rs
Recorded, anonymized payloads fixtures/ (realtime ones by tools/record-realtime.mjs)

KickTracker's own copy of this file, its recorder and its simulator are in that repository (KICK.md, sim/).