Events API
Real-time event streaming for your channel. Perfect for stream tools, bots, and integrations like Streamer.bot.
No webhook server required
Unlike webhooks, the Events API doesn't require you to host a server. Your application connects directly to Velora and receives events in real-time. This is ideal for desktop applications, stream tools, and situations where hosting a webhook endpoint isn't practical.
Overview
The Events API provides two ways to receive real-time events for your channel:
WebSocket
Persistent bidirectional connection using Socket.IO
wss://api.velora.tv/ws/eventsServer-Sent Events (SSE)
HTTP-based streaming for simpler clients
GET /api/events/streamAuthentication
Both connection methods authenticate with a standard OAuth access token — the same token your app already uses for chat and REST. There is no separate "broadcaster app" to register: any user who authorizes your app becomes a broadcaster you can receive events for. No special scopes are required.
No special permissions needed. Any valid access token can receive events for the channel that issued it. If your token already works for chat/REST, it works here.
Getting an Access Token
Use the standard OAuth flow to get a user's access token. See the Authentication Guide for details.
Before the OAuth flow will work: your app needs at least one Redirect URI registered (Developer Dashboard → your app → Edit details). Without one, the authorize URL has nowhere to return and will error.
WebSocket Connection
The WebSocket endpoint uses Socket.IO for reliable connections with automatic reconnection.
Connection URL
wss://api.velora.tv/ws/eventsConnection Example (JavaScript)
import { io } from 'socket.io-client';
const socket = io('wss://api.velora.tv/ws/events', {
auth: {
token: 'YOUR_ACCESS_TOKEN'
},
// Or pass token as query parameter:
// query: { token: 'YOUR_ACCESS_TOKEN' }
});
// Connection established. With a valid token you are AUTO-SUBSCRIBED to your
// own channel — no extra subscribe call is needed; your events start flowing.
socket.on('connected', (data) => {
console.log('Connected to Events API');
console.log('Channel:', data.channelUsername); // your channel's username
console.log('Authenticated:', data.authenticated); // true when a valid token was sent
console.log('Auto-subscribed:', data.autoSubscribed);
});
// Three ways to receive events — use whichever fits:
// 1) A single generic handler for ALL events ({ event, timestamp, data } envelope):
socket.on('event', ({ event, data, timestamp }) => {
console.log('Event:', event, data, timestamp);
});
// 2) socket.onAny — also catches every event, by name:
socket.onAny((eventName, payload) => {
console.log('Event:', eventName, payload);
});
// 3) Listen for specific events by name (payload is the raw event data):
socket.on('channel.follow', (payload) => {
console.log('New follower:', payload);
});
socket.on('channel.subscribe', (payload) => {
console.log('New subscriber:', payload);
});
// Handle disconnection
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});Connection Example (C# / Streamer.bot)
using SocketIOClient;
var socket = new SocketIO("wss://api.velora.tv/ws/events", new SocketIOOptions
{
Auth = new { token = "YOUR_ACCESS_TOKEN" }
});
socket.On("connected", response =>
{
var data = response.GetValue<ConnectedData>();
Console.WriteLine($"Connected as {data.ChannelUsername}");
});
socket.On("event", response =>
{
var payload = response.GetValue<EventPayload>();
Console.WriteLine($"Event: {payload.Event}");
// Handle the event...
});
await socket.ConnectAsync();Server-Sent Events (SSE)
SSE is a simpler alternative that works over standard HTTP. Great for languages without good Socket.IO support.
Endpoint
GET https://api.velora.tv/api/events/streamHeaders
| Header | Value |
|---|---|
Authorization | Bearer YOUR_ACCESS_TOKEN |
Accept | text/event-stream |
Optional: Filter Events
You can filter which events you receive using the events query parameter:
GET /api/events/stream?events=channel.follow,channel.subscribe,chat.messageConnection Example (JavaScript)
const eventSource = new EventSource(
'https://api.velora.tv/api/events/stream',
{
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
}
}
);
// Connection established
eventSource.addEventListener('connected', (e) => {
const data = JSON.parse(e.data);
console.log('Connected:', data.channelUsername);
});
// Listen for specific event types
eventSource.addEventListener('channel.follow', (e) => {
const payload = JSON.parse(e.data);
console.log('New follower:', payload.data.username);
});
eventSource.addEventListener('channel.subscribe', (e) => {
const payload = JSON.parse(e.data);
console.log('New sub:', payload.data.username);
});
// Or listen for all events
eventSource.onmessage = (e) => {
const payload = JSON.parse(e.data);
console.log('Event:', payload.event, payload.data);
};
eventSource.onerror = (e) => {
console.error('SSE error:', e);
};Connection Example (curl)
curl -N -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: text/event-stream" \
"https://api.velora.tv/api/events/stream"Event Format
All events follow a consistent format:
{
"event": "channel.follow",
"timestamp": "2026-02-10T12:00:00.000Z",
"data": {
"userId": "user_abc123",
"username": "newviewer",
"displayName": "NewViewer",
"followedAt": "2026-02-10T12:00:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
event | string | The event type (e.g., "channel.follow") |
timestamp | string | ISO 8601 timestamp when the event occurred |
data | object | Event-specific payload data |
Available Events
Stream Events
| Event | Description |
|---|---|
stream.online | Stream goes live |
stream.offline | Stream ends |
stream.update | Stream title or category changes |
Chat Events
| Event | Description |
|---|---|
chat.message | Chat message sent in your channel (includes regular messages and card messages like stickers/sounds) |
chat.message Payload
| Field | Type | Description |
|---|---|---|
messageId | string | Unique message identifier |
userId | string | Sender's user ID |
username | string | Sender's username |
displayName | string | Sender's display name |
message | string | The message text content |
badges | string[] | Badge slugs for this user |
isMod | boolean | Whether the user is a moderator |
isVip | boolean | Whether the user is a VIP |
isSubscriber | boolean | Whether the user is subscribed |
subscriberMonths | number? | Subscription tenure in months (if subscribed) |
color | string? | User's accent color (hex) |
mentionsAdded 2026-03-02 | Array<{username, userId, displayName}>? | Resolved @mentions found in the message. Only present when the message contains valid @username mentions. |
cardAdded 2026-03-02 | object? | Present on card messages (stickers, sounds, celebrations). Contains type and payload. |
isSystem | boolean? | True for system/card messages |
Card types include: sticker-send, sound-send, emote-sticker-send, volts-celebration, subscription-celebration, gift-celebration, and more. Check data.card.type to identify the card type.
Channel Events
| Event | Description |
|---|---|
channel.follow | Someone follows the channel |
channel.subscribe | New or renewed subscription |
channel.subscription.gift | Gift subscription(s) given |
channel.volts | User sends Volts |
channel.raid | Channel receives a raid |
channel.ban | User banned from channel |
channel.unban | User unbanned from channel |
channel.moderator.add | Moderator added |
channel.moderator.remove | Moderator removed |
channel.channel_points_redemption | Channel points reward redeemed |
Interaction Events
| Event | Description |
|---|---|
channel.poll.begin | Poll starts |
channel.poll.end | Poll ends |
channel.prediction.begin | Prediction starts |
channel.prediction.lock | Prediction betting locks |
channel.prediction.end | Prediction ends/resolves |
channel.hype_train.begin | Hype train starts |
channel.hype_train.progress | Hype train level increases |
channel.hype_train.end | Hype train ends |
Fetching Channel Point Rewards
To build a bot that responds to channel point redemptions, you'll want to know what rewards a channel has configured. You can fetch them via REST API or directly through WebSocket.
Option 1: REST API Endpoint
GET https://api.velora.tv/api/channel-points/{channelId}/items/with-built-inNo authentication required. This endpoint is public, so you can fetch rewards without an access token.
Option 2: WebSocket Request
If you're already connected via WebSocket, you can request the rewards list directly:
// Request rewards over WebSocket. Pass your channelId (from the 'connected'
// payload) — without it the request returns an empty list.
socket.emit('channel:getRewards', { channelId: myChannelId });
// Listen for the response. The payload is an OBJECT with a 'rewards' array;
// each item matches the REST shape: { id, name, cost, description, iconUrl,
// enabled, builtInType }.
socket.on('channel:rewards', ({ rewards }) => {
console.log('Channel rewards:', rewards);
// [
// { id: '...', name: 'Highlight My Message', cost: 500, enabled: true, builtInType: null },
// { id: '...', name: 'First', cost: 1, enabled: true, builtInType: 'first' }
// ]
});
// Handle errors
socket.on('error', (err) => {
if (err.code === 'SERVICE_UNAVAILABLE') {
// Channel points service not available
}
});Example REST Response
{
"items": [
{
"id": "reward_abc123",
"name": "Highlight My Message",
"cost": 500,
"description": "Make your message stand out!",
"iconUrl": "https://...",
"builtInType": null, // null = custom reward
"enabled": true,
"requiresModeratorApproval": false,
"maxPerStream": null,
"maxPerUserPerStream": 3
},
{
"id": "reward_xyz789",
"name": "First",
"cost": 1,
"description": "Be the first to claim this each stream!",
"builtInType": "first", // Built-in reward type
"enabled": true
}
]
}WebSocket Response Format
The WebSocket channel:rewards response is an object with a rewards array. Each item matches the REST shape:
{
"rewards": [
{
"id": "reward_abc123",
"name": "Highlight My Message",
"cost": 500,
"description": "Make your message stand out!",
"iconUrl": "https://...",
"enabled": true,
"builtInType": null
},
{
"id": "reward_xyz789",
"name": "First",
"cost": 1,
"description": "Be the first to claim this each stream!",
"iconUrl": null,
"enabled": true,
"builtInType": "first"
}
]
}Matching Redemptions to Rewards
When you receive a channel.channel_points_redemption event, it includes the rewardId and rewardTitle. Match these against your fetched rewards list to trigger the appropriate action:
// Redemption event payload
{
"event": "channel.channel_points_redemption",
"timestamp": "2026-02-10T12:00:00Z",
"data": {
"redemptionId": "red_abc123",
"rewardId": "reward_abc123", // Match this to your rewards list
"rewardTitle": "Highlight My Message",
"rewardCost": 500,
"userId": "user_xyz",
"username": "viewer123",
"displayName": "Viewer123",
"userInput": "Check out my message!", // If reward accepts input
"status": "unfulfilled",
"redeemedAt": "2026-02-10T12:00:00Z"
}
}Events API vs Webhooks
| Feature | Events API | Webhooks |
|---|---|---|
| Server required | No | Yes |
| Desktop app friendly | Yes | No |
| Connection type | Persistent (outbound) | On-demand (inbound) |
| Latency | Lower (real-time) | Slightly higher |
| Retry handling | Automatic reconnect | Server retries |
| Best for | Stream tools, bots, desktop apps | Web servers, automation |