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 use your broadcaster OAuth access token for authentication. No special scopes are required—if you have a valid token for a broadcaster, you can receive their events.
No special permissions needed. Any valid broadcaster access token can receive events for that channel.
Getting an Access Token
Use the standard OAuth flow to get a broadcaster's access token. See the Authentication Guide for details.
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
socket.on('connected', (data) => {
console.log('Connected to Events API');
console.log('Channel:', data.channelUsername);
});
// Listen for all events
socket.on('event', (payload) => {
console.log('Event received:', payload.event);
console.log('Data:', payload.data);
console.log('Timestamp:', payload.timestamp);
});
// Handle errors
socket.on('error', (err) => {
console.error('Connection error:', err.message);
});
// 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
socket.emit('channel:getRewards');
// Listen for the response
socket.on('channel:rewards', (rewards) => {
console.log('Channel rewards:', rewards);
// [
// { id: '...', name: 'Highlight My Message', cost: 500, ... },
// { id: '...', name: 'First', cost: 1, 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 returns an array directly:
[
{
"id": "reward_abc123",
"name": "Highlight My Message",
"cost": 500,
"description": "Make your message stand out!",
"iconUrl": "https://...",
"enabled": true,
"isBuiltIn": false,
"builtInType": null
},
{
"id": "reward_xyz789",
"name": "First",
"cost": 1,
"description": "Be the first to claim this each stream!",
"iconUrl": null,
"enabled": true,
"isBuiltIn": 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 |