Guide
Chat Integration
One endpoint does most of the work: POST /chat/channels/:channelId/messages. With a chat:write token your app posts as the person who authorised it — that is enough to build a bot, an alert relay, or a moderation tool. Everything below is that call, its options, and the surfaces around it.
With the Chat API your application can:
- Send chat messages on behalf of authenticated users
- Apply message effects like glow, rainbow, galaxy, and gigantify
- Reply to specific messages with threading
- Build interactive chat bots and alert systems
Required scopes
A token carries exactly the permission its scope names. Reading history, posting, and moderating are three separate grants.
| Scope | Purpose | Rate limit |
|---|---|---|
chat:read | Read chat messages and history | — |
chat:write | Send chat messages | 30 req/min |
chat:moderate | Delete messages, timeout, ban users | 60 req/min |
Send a chat message
/api/integrations/oauth/chat/channels/:channelId/messagesSend a chat message to a specific channel. The message will be sent as the authenticated user.
URL parameters
| Parameter | Description |
|---|---|
channelId | The user ID of the channel to send the message to |
Request body
| Field | Type | Description |
|---|---|---|
message | string | The message content (max 500 characters) |
sendAsBot | boolean? | Post as your connected bot instead of the authenticated user. Needs this route’s chat:write scope and either a bot-type app or a bot connected via /integrations/oauth/bot/select. Omit or set to false to post as the authenticated user (default). |
effect | string? | Message effect: glow, galaxy, rainbow, gigantify |
effectColor | string? | Hex color for the effect (e.g., #ff6b6b) |
replyTo | object? | Reply to a specific message (see below) |
replyTo object
| Field | Type | Description |
|---|---|---|
messageId | string | ID of the message being replied to |
username | string | Username of the original message author |
snippet | string | Preview of the original message (max 100 chars) |
curl -X POST https://api.velora.tv/api/integrations/oauth/chat/channels/user-uuid-123/messages \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Hello from my integration!",
"effect": "glow",
"effectColor": "#ff6b6b"
}'{
"success": true,
"messageId": "msg-uuid-456"
}Replying to messages
You can reply to specific messages by including the replyTo object:
curl -X POST https://api.velora.tv/api/integrations/oauth/chat/channels/user-uuid-123/messages \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Thanks for the follow!",
"replyTo": {
"messageId": "original-msg-uuid",
"username": "newviewer",
"snippet": "Just followed, love your content!"
}
}'Tip: The snippet field is displayed as a preview of the original message in the reply UI.
Message effects
Velora chat supports special message effects that make messages stand out:
glow
Adds a subtle glow around the message text
"effect": "glow", "effectColor": "#ff6b6b"CSS: text-shadow: 0 0 10px {effectColor}
galaxy
Animated starfield background behind the message
"effect": "galaxy"Renders animated particles behind the message container
rainbow
Cycling rainbow colors on the text
"effect": "rainbow"CSS: Animated gradient with -webkit-background-clip: text
gigantify
Large bouncing text animation
"effect": "gigantify"CSS: font-size: 2em with bounce animation
Complete example
Here's a complete JavaScript example for building a simple chat bot:
// Velora Chat Bot Example
const VELORA_API = 'https://api.velora.tv/api/integrations/oauth';
class VeloraChatBot {
constructor(accessToken) {
this.accessToken = accessToken;
}
async sendMessage(channelId, message, options = {}) {
const response = await fetch(
`${VELORA_API}/chat/channels/${channelId}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
// Set sendAsBot: true to post as your connected bot (needs
// chat:write, this route's scope, plus a bot connected via
// /integrations/oauth/bot/select or a bot-type app).
// Omit/false to post as the authenticated user.
sendAsBot: options.sendAsBot,
effect: options.effect,
effectColor: options.effectColor,
replyTo: options.replyTo,
}),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to send message');
}
return response.json();
}
// Send a welcome message with glow effect
async welcomeUser(channelId, username) {
return this.sendMessage(
channelId,
`Welcome to the stream, @${username}! 🎉`,
{ effect: 'glow', effectColor: '#4ecdc4' }
);
}
// Send a follow thank you with rainbow
async thankFollower(channelId, username) {
return this.sendMessage(
channelId,
`Thank you for the follow, @${username}! ❤️`,
{ effect: 'rainbow' }
);
}
// Send a sub alert with gigantify
async celebrateSub(channelId, username, months) {
const emoji = months > 1 ? '🏆' : '🎊';
return this.sendMessage(
channelId,
`${emoji} @${username} just subscribed for ${months} month(s)! ${emoji}`,
{ effect: 'gigantify' }
);
}
// Reply to a message
async replyTo(channelId, originalMessage, reply) {
return this.sendMessage(channelId, reply, {
replyTo: {
messageId: originalMessage.id,
username: originalMessage.username,
snippet: originalMessage.content.substring(0, 100),
},
});
}
}
// Usage Example
//
// BOTH values below are PLACEHOLDERS — replace them with real values.
//
// 1) The token is an OAuth ACCESS TOKEN obtained through the authorize flow
// with the streamer's consent. There is no "bot token" to generate, and
// client ID + secret alone will NOT work (no client-credentials grant).
// Access tokens expire — refresh them, don't hard-code one.
//
// 2) channelId is the STREAMER'S USER ID (a UUID). On Velora a user's id IS
// their channel id, so this is the account whose chat you're joining —
// NOT their username, and not the literal string below.
// Get it from GET /api/users/{username} — the id field on the response.
// Usernames are case-sensitive. You can also read it from the
// channelId field on any webhook event you receive for that channel.
const bot = new VeloraChatBot('your_oauth_access_token');
const channelId = 'streamer-user-id'; // a UUID, e.g. '3fa85f64-5717-4562-b3fc-2c963f66afa6'
// Welcome a new viewer
bot.welcomeUser(channelId, 'NewViewer123')
.then(result => console.log('Message sent:', result.messageId))
.catch(err => console.error('Error:', err.message));
// React to events from webhooks
async function handleWebhookEvent(event) {
if (event.event === 'user.follow') {
await bot.thankFollower(
event.data.channel.id,
event.data.follower.username
);
}
if (event.event === 'channel.subscribe') {
await bot.celebrateSub(
event.data.channel.id,
event.data.subscriber.username,
event.data.months
);
}
}Error handling
| Status | Meaning |
|---|---|
401 | Invalid or expired access token |
403 | User is banned/timed out in the channel, or missing chat:write scope |
404 | Channel not found |
429 | Rate limit exceeded (30 messages/minute for chat:write) |
Chat settingsAdded May 9, 2026
Manage channel chat modes programmatically. Requires stream:read (GET) or stream:write (PATCH) scope.
GET /api/integrations/oauth/chat/settings
Authorization: Bearer YOUR_ACCESS_TOKEN
Response:
{
"slowMode": false,
"slowModeSeconds": 0,
"followersOnly": false,
"subscribersOnly": false,
"emoteOnly": false
}PATCH /api/integrations/oauth/chat/settings
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"slowMode": true,
"slowModeSeconds": 5,
"followersOnly": false,
"subscribersOnly": false,
"emoteOnly": false
}| Field | Type | Description |
|---|---|---|
slowMode | boolean | Enable/disable slow mode |
slowModeSeconds | number | Seconds between messages (0 = disabled) |
followersOnly | boolean | Only followers can chat |
subscribersOnly | boolean | Only subscribers can chat |
emoteOnly | boolean | Only emotes allowed in chat |
Test event sandboxAdded May 9, 2026
Test your bot's event handlers without real money or subscriptions. The sandbox fires synthetic events through the same socket pipeline as real events.
Sandbox mode — no real transactions
- Does NOT create any database records
- Does NOT touch Stripe or move real money
- Does NOT create real subscriptions
- Does NOT affect analytics or dashboard numbers
- Every test event includes
"isTest": truein the payload
POST /api/integrations/oauth/test/events
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"eventType": "subscription",
"channelId": "YOUR_CHANNEL_ID",
"tier": "tier1",
"amount": 499,
"message": "Test sub!"
}Available event types
| Event type | Description | Extra params |
|---|---|---|
subscription | New subscription | tier, amount, message |
gift_subscription | Gifted subscription(s) | tier, amount, quantity |
follow | New follower | — |
volt_tip | Volt tip | amount, message |
raid | Incoming raid | amount (viewer count) |
chat_message | Chat message | message |
POST /api/integrations/oauth/test/events/types Authorization: Bearer YOUR_ACCESS_TOKEN