Chat WebSocket API
Build chatbots, moderation tools, and real-time chat clients that connect directly to Velora's chat infrastructure.
Heads up — namespace matters
The chat WebSocket lives on the /chat namespace. If you connect to the root URL (wss://api.velora.tv/socket.io/) without a namespace, the server will accept your handshake then disconnect you within seconds because nothing is listening there. Connect to wss://api.velora.tv/chat instead.
Overview
Velora's chat runs on Socket.IO. You can connect as either a guest (read-only — receive messages but can't send) or as an authenticated user (full chat permissions, scoped by your account's role and channel-specific roles like moderator/VIP).
If you're building a bot, you'll want the authenticated path. Bots authenticate using either:
- A user JWT (the same kind a logged-in browser uses) — fastest path to ship
- An OAuth 2.0 access token from a registered developer app — proper for distributed/published bots
Endpoint
wss://api.velora.tv/chatSocket.IO transport. Both websocket and polling work; websocket is recommended for bots.
Authentication
Pass your JWT or OAuth access token using anyof three methods (the gateway checks all three on connection — first one that's present wins):
1. Socket.IO auth payload (recommended)
import { io } from 'socket.io-client';
const socket = io('wss://api.velora.tv/chat', {
auth: { token: 'YOUR_JWT_OR_OAUTH_TOKEN' },
transports: ['websocket'],
});2. Authorization header
const socket = io('wss://api.velora.tv/chat', {
extraHeaders: { Authorization: 'Bearer YOUR_TOKEN' },
transports: ['websocket'],
});3. Query string
const socket = io('wss://api.velora.tv/chat?token=YOUR_TOKEN', {
transports: ['websocket'],
});If no token is sent, you'll connect as a guest (read-only). Authenticated connections receive your role, account-wide permissions, and channel-specific roles automatically — no separate login event needed.
Joining a channel
Authenticating connects you to the gateway. To start receiving messages and events for a specific channel, emit joinChannel:
socket.emit('joinChannel', { channelId: 'streamer_user_id_or_username' });channelIdcan be either the broadcaster's user ID (UUID) or their username — the server resolves both. You can join multiple channels concurrently with the same socket. To leave: socket.emit('leaveChannel', { channelId }).
Sending messages
socket.emit('sendMessage', {
channelId: 'streamer_user_id_or_username',
message: 'Hello from my bot!',
// Optional fields:
platform: 'velora', // 'velora' (default), 'twitch', 'kick', 'youtube'
effect: 'rainbow', // optional message effect
effectColor: '#ff00ff', // optional color override
replyTo: { // optional reply context
messageId: 'parent_msg_id',
username: 'parent_author',
snippet: 'Parent message preview',
},
});Server enforces: 500-char max, slow-mode/follower-only/sub-only/emote-only checks per channel, blacklist filtering, and email-verification gating. You'll receive a commandResult event back if your message is rejected.
Common events to listen for
The gateway emits a wide variety of events. The most useful ones for bots and chat clients:
newMessageA new chat message arrived. Payload includes id, sender, content, badges, channelRole, replyTo.
userJoinedA user joined a channel you're subscribed to.
userLeftA user left.
userBannedA user was banned from a channel — clients should purge their visible messages.
userTimedOutIncludes durationSeconds + expiresAt.
bannedViewerJoined / bannedViewerLeftMod-only — channel-banned-but-still-watching viewers (broadcaster + moderators only receive these, regular viewers don't).
messagePinnedA pinned message went up; payload has the message and TTL.
raidSessionStarted / raidControlResultOutbound raid lifecycle.
viewer_count_updateLive viewer count for the channel.
moderationNoticeTargeted notice (ban, timeout, etc.) sent to the affected user.
commandResultResult of a sendMessage / slash command — check success and message fields.
chatClearedA moderator cleared chat history; clients should clear their local message buffer.
Building a bot
For published / multi-user bots, register a developer app first — see Getting Started and Authentication. Your bot then uses an OAuth access token per-streamer to connect on their behalf.
For self-hosted single-streamer bots, you can use a long-lived JWT. We recommend the OAuth path even for single-streamer bots so token rotation and scopes work cleanly.
User IDs starting with bot:, streamerbot:, or devapp: are flagged with isBot: true in their newMessage payloads, so chat clients can render bot messages differently if they want.
Need to receive non-chat events too (subscriptions, raids, channel-points redemptions, donations)? Use the Events API in addition to the chat WebSocket — they complement each other.
Streamer.bot integration
Velora ships a first-class Streamer.bot bridge. If you're writing a Streamer.bot action that needs to send a Velora chat message, use the bridge instead of speaking Socket.IO directly:
POST https://api.velora.tv/streamerbot/messages
Authorization: Bearer <streamerbot-config-token>
Content-Type: application/json
{
"channelId": "your_user_id",
"message": "Hello from Streamer.bot!"
}Configure the bridge token in your dashboard under Settings → Integrations → Streamer.bot. The bridge handles the WebSocket connection, retries, and rate-limit backoff for you.
Slash commands in Streamer.bot: The bridge automatically recognizes slash commands (messages starting with /) and executes them with the channel owner's permissions. For example, sending "/announceblue Stream starting soon!" via the bridge will execute the announcement command as if the streamer typed it.
Slash commands
Velora supports slash commands for channel moderation and announcements. When sent via the Streamer.bot bridge or from a channel-owned bot, these commands execute with the channel owner's permissions.
Announcement commands
Post styled announcements in chat that are instantly recognizable and attention-grabbing. Perfect for stream start/end notices, important updates, or calls to action.
Base command (channel accent color)
/announce <message>Alias: /ann
Colored variants
/announceblue <message> — alias: /annblue/announcegold <message> — alias: /anngold/announcegreen <message> — alias: /anngreen/announcered <message> — alias: /annred/announceorange <message> — alias: /annorange/announcecoral <message> — alias: /anncoralRequirements & limits
- Minimum role: moderator
- Max length: 500 characters
- Markdown formatting supported in message text
Example: Streamer.bot announcement
POST https://api.velora.tv/streamerbot/messages
Authorization: Bearer <your-token>
Content-Type: application/json
{
"channelId": "your_user_id",
"message": "/announceblue **Stream starting in 5 minutes!** Get ready!"
}More slash commands are documented in the dashboard under Chat Settings → Commands. Common ones include /timeout, /ban, /mod, /vip, and /raid.
Reconnection & heartbeats
Socket.IO clients reconnect automatically. We accept a heartbeatemit at any cadence to keep your session warm; the gateway responds with the current channel members. If you don't emit anything for a long stretch we may drop you — emit a heartbeat or a ping every 60-120s if your client is otherwise quiet.
After a reconnect, your joinChannelsubscriptions need to be re-emitted — they're scoped to the socket lifetime, not the user. A reconnect handler that re-joins your channel(s) is one of the first things to write.
Common gotchas
"I connect, then disconnect within seconds"
You're connecting to the wrong namespace. Use wss://api.velora.tv/chat, NOT wss://api.velora.tv/socket.io/ with no namespace.
"My messages are getting blocked"
Listen for commandResult — it tells you exactly why a message was rejected (slow-mode, sub-only, email-verification, etc.). The most common surprise is email-verification: bot accounts must have a verified email address attached.
"I'm receiving events for the wrong channel"
You forgot to leave a previous channel before joining a new one, or your bot is in multiple channels and needs to filter on the event payload's channelId.
"CORS errors from a browser-based client"
The gateway accepts connections from *.velora.tv, localhost, and mobile app schemes (capacitor://, ionic://, file://). For other origins, use the OAuth-via-server pattern instead of speaking from the browser directly.
Need help?
File an issue on Nexus, or join the Velora Discord and ping a CM in #cm-workspace. If you're hitting a specific event/payload shape mismatch with what's documented here, screenshot the full client-side log and include the connection URL — that'll get an answer fast.