Chat
The chat system is powered by a websocket server. Please read the entire page before implementing anything, as there are some important notes.
Connection and messages
Section titled “Connection and messages”The websocket server is located at wss://hackclub.tv/api/chat/ws/:username, where :username is the channel you want to connect to.
You’ll need to provide authentication, which can be done by providing an auth_session cookie, just like the REST API.
Once connected, you must implement a subroutine in your code to send ping messages every about 5 seconds. This is because of Cloudflare limitations.
Messages are sent and received in JSON format. The following message types are supported:
-
session: sent by the server immediately upon connection.-
received by client:
{"type": "session","viewer": {"id": "user_id","username": "your_username"},"permissions": {"canModerate": false},"moderation": {"hasBlockedTerms": false,"slowModeSeconds": 0,"maxMessageLength": 400}}viewerisnullfor unauthenticated (grant-only) connections.canModerateistruefor channel owners, managers, moderators, and platform admins.
-
-
chatAccess: sent by the server on connect (for authenticated non-bot users) and whenever a user’s restriction state changes.-
received by client:
{"type": "chatAccess","canSend": true,"restriction": null}When the user is restricted,
canSendisfalseandrestrictioncontains:{"type": "timeout","reason": "Timed out by moderator","expiresAt": "2026-01-01T00:00:00.000Z"}typeis either"timeout"or"ban".expiresAtis an ISO 8601 string for timeouts, ornullfor permanent bans.
-
-
ping: a ping message to keep the connection alive.-
sent by client:
{"type": "ping"} -
received by client:
{"type": "pong"}
-
-
message: a chat message.-
sent by client:
{"type": "message","message": "Hello, world!"} -
received by client (broadcast to all viewers of the channel):
{"type": "message","msgId": "uuid-v4","user": {"id": "user_id","username": "user_who_sent_message","pfpUrl": "https://example.com/avatar.png","displayName": "Display Name","isBot": false,"isPlatformAdmin": false,"channelRole": null},"message": "Hello, world!"}channelRoleis one of"owner","manager","chatModerator","botModerator", ornull.displayNamemay beundefinedfor regular users.
-
-
history: the recent chat history, sent upon connection.-
received by client:
{"type": "history","messages": [{"type": "message","msgId": "uuid-v4","user": {"id": "user_id","username": "user_who_sent_message","pfpUrl": "https://example.com/avatar.png","displayName": "Display Name","isBot": false,"isPlatformAdmin": false,"channelRole": null},"message": "Hello, world!"}]}Up to 100 messages are returned. Each message has the same shape as a received
messageevent.
-
-
systemMsg: a system notification broadcast to all viewers, e.g. when a user is banned or unbanned.-
received by client:
{"type": "systemMsg","message": "username was banned."}
-
-
moderationError: sent to the acting client when a message or moderation action is rejected.-
received by client:
{"type": "moderationError","code": "RATE_LIMIT","message": "You are sending messages too fast.","restriction": null}restrictionis only present (non-null) forTIMED_OUTandBANNEDcodes, and has the same shape as therestrictionfield inchatAccess. Possible codes:
Code Trigger FORBIDDENNot permitted to perform the action RATE_LIMITToo many messages in the rate limit window SLOW_MODESent before the slow mode cooldown expired TIMED_OUTUser is currently timed out BANNEDUser is permanently banned MESSAGE_TOO_LONGMessage exceeds maxMessageLengthBLOCKED_TERMMessage contains a blocked term INVALID_TARGETModeration target is invalid or does not exist INVALID_REQUESTMalformed moderation command NOT_FOUNDTarget message not found (delete) -
Moderation commands
Section titled “Moderation commands”moderation commands are only available to authenticated users with the canModerate permission (owner, manager, chatModerator, botModerator, or platform admin). sending any of these without permission returns a moderationError with code FORBIDDEN.
obviously, role hierarchy is enforced: a chatModerator cannot moderate a manager or owner. Platform admins bypass hierarchy checks entirely.
-
mod:deleteMessage: delete a message from the chat history and broadcast its removal.-
sent by client:
{"type": "mod:deleteMessage","msgId": "uuid-of-message-to-delete"} -
received by all clients on success:
{"type": "messageDeleted","msgId": "uuid-of-message-to-delete"}
-
-
mod:timeoutUser: temporarily restrict a user from sending messages.-
sent by client:
{"type": "mod:timeoutUser","targetUserId": "user_id","durationSeconds": 300,"reason": "Optional reason"}durationSecondsis clamped between 10 and 86400 (24 hours). Defaults to 300 if omitted. On success, asystemMsgis broadcast and the target receives achatAccessupdate.
-
-
mod:banUser: permanently ban a user from sending messages.-
sent by client:
{"type": "mod:banUser","targetUserId": "user_id","reason": "Optional reason"}On success, a
systemMsgis broadcast and the target receives achatAccessupdate.
-
-
mod:liftTimeout/mod:unbanUser: remove an active timeout or ban.-
sent by client:
{"type": "mod:liftTimeout","targetUserId": "user_id"}Both types behave identically and remove any active restriction for the target user. On success, a
systemMsgis broadcast and the target receives achatAccessupdate withcanSend: true.
-
Emoji handling
Section titled “Emoji handling”diagram source: devin deepwiki
graph TB
subgraph "Emoji Processing Pipeline"
CHAT_MSG["Chat Message"]
PATTERN_MATCH["Regex :emoji: Pattern"]
EMOJI_REQUEST["emojiMsg WebSocket"]
REDIS_LOOKUP["Redis HGET emojis"]
FUZZY_SEARCH["uFuzzy"]
EMOJI_RESPONSE["emojiMsgResponse"]
end
subgraph "Redis Storage"
EMOJI_HASH["emojis hash key"]
EMOJI_PREFIXED["emoji:{name} url"]
EMOJIS_PREFIXED["emojis:{name} url"]
end
CHAT_MSG --> PATTERN_MATCH
PATTERN_MATCH --":emojiname:"--> EMOJI_REQUEST
EMOJI_REQUEST --> REDIS_LOOKUP
REDIS_LOOKUP --> EMOJI_HASH
REDIS_LOOKUP --> EMOJI_PREFIXED
REDIS_LOOKUP --> EMOJIS_PREFIXED
REDIS_LOOKUP --> EMOJI_RESPONSE
FUZZY_SEARCH --> EMOJI_HASH
FUZZY_SEARCH --"search results"--> EMOJI_RESPONSE
When a chat message is sent, the server looks for patterns in the format :emojiname: using regex. For each match, it sends a request to the emojiMsg WebSocket.
The server then checks Redis for the emoji URL and returns it.
When a user wants to look up an emoji (by typing :(partial name)), the server uses uFuzzy to find matching emojis in the Redis emojis hash key and returns the results.
Here’s what gets sent on the websocket:
-
emojiMsg: Looks up emojis-
sent by client:
{"type": "emojiMsg","emojis": ["aga", "yapa", "heavysob", "yay", "yay-bounce"]} -
received by client:
{"type": "emojiMsgResponse","emojis": {"aga": "https://emoji.slack-edge.com/aga.png","yapa": "https://emoji.slack-edge.com/yapa.png","heavysob": "https://emoji.slack-edge.com/heavysob.png","yay": "https://emoji.slack-edge.com/yay.png","yay-bounce": "https://emoji.slack-edge.com/yay-bounce.png"}}
-
-
emojiSearch: Searches for emojis-
sent by client:
{"type": "emojiSearch","searchTerm": "aga"} -
received by client:
{"type": "emojiSearchResponse","results": ["aga","aga-brick-throw","aga-dance","aga-transparent","a-aga","a-aga-transparent","agaban","agaboing","agabounce","agabusiness"]}
-