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/events

Server-Sent Events (SSE)

HTTP-based streaming for simpler clients

GET /api/events/stream

Authentication

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/events

Connection 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/stream

Headers

HeaderValue
AuthorizationBearer YOUR_ACCESS_TOKEN
Accepttext/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.message

Connection 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"
  }
}
FieldTypeDescription
eventstringThe event type (e.g., "channel.follow")
timestampstringISO 8601 timestamp when the event occurred
dataobjectEvent-specific payload data

Available Events

Stream Events

EventDescription
stream.onlineStream goes live
stream.offlineStream ends
stream.updateStream title or category changes

Chat Events

EventDescription
chat.messageChat message sent in your channel (includes regular messages and card messages like stickers/sounds)

chat.message Payload

FieldTypeDescription
messageIdstringUnique message identifier
userIdstringSender's user ID
usernamestringSender's username
displayNamestringSender's display name
messagestringThe message text content
badgesstring[]Badge slugs for this user
isModbooleanWhether the user is a moderator
isVipbooleanWhether the user is a VIP
isSubscriberbooleanWhether the user is subscribed
subscriberMonthsnumber?Subscription tenure in months (if subscribed)
colorstring?User's accent color (hex)
mentionsAdded 2026-03-02Array<{username, userId, displayName}>?Resolved @mentions found in the message. Only present when the message contains valid @username mentions.
cardAdded 2026-03-02object?Present on card messages (stickers, sounds, celebrations). Contains type and payload.
isSystemboolean?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

EventDescription
channel.followSomeone follows the channel
channel.subscribeNew or renewed subscription
channel.subscription.giftGift subscription(s) given
channel.voltsUser sends Volts
channel.raidChannel receives a raid
channel.banUser banned from channel
channel.unbanUser unbanned from channel
channel.moderator.addModerator added
channel.moderator.removeModerator removed
channel.channel_points_redemptionChannel points reward redeemed

Interaction Events

EventDescription
channel.poll.beginPoll starts
channel.poll.endPoll ends
channel.prediction.beginPrediction starts
channel.prediction.lockPrediction betting locks
channel.prediction.endPrediction ends/resolves
channel.hype_train.beginHype train starts
channel.hype_train.progressHype train level increases
channel.hype_train.endHype 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-in

No 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

FeatureEvents APIWebhooks
Server requiredNoYes
Desktop app friendlyYesNo
Connection typePersistent (outbound)On-demand (inbound)
LatencyLower (real-time)Slightly higher
Retry handlingAutomatic reconnectServer retries
Best forStream tools, bots, desktop appsWeb servers, automation

Related Documentation