Developer API

Docs
Back to Dashboard

Build custom overlays, integrations, and applications using the Ninja Chatter API.

Quick Start

The easiest way to receive chat messages for overlays is using the read-only stream endpoints. No authentication required!

1. WebSocket Stream (Recommended)

const ws = new WebSocket('wss://api.ninjachatter.com/stream/YOUR_ROOM_ID');
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  console.log(`${msg.user.name}: ${msg.payload.text}`);
};

2. SSE Stream (Simpler)

const events = new EventSource('https://api.ninjachatter.com/stream/YOUR_ROOM_ID/sse');
events.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  console.log(`${msg.user.name}: ${msg.payload.text}`);
};

Read-Only Stream API

These endpoints provide read-only access to room chat messages. Perfect for building overlays, bots, and integrations.

Room owners can disable read-only stream access in the Dashboard under Room Settings > "Allow read-only stream access".

Endpoints

EndpointProtocolFormatDescription
/stream/:room WS Clean JSON WebSocket stream with our Envelope format
/stream/:room/sse GET Clean JSON Server-Sent Events stream
/stream/:room/ssn WS SSN Format WebSocket stream in SSN-compatible format

Features

SSN Overlay Integration

If you have existing SocialStream.ninja overlays, you can point them at our API with minimal changes.

Using SSN dock.html

Add the server parameter to your dock.html URL:

https://socialstream.ninja/dock.html?session=YOUR_ROOM_ID&server=wss://api.ninjachatter.com/stream/YOUR_ROOM_ID/ssn
Replace YOUR_ROOM_ID with your actual room ID from the Dashboard.

SSN Message Format

The /stream/:room/ssn endpoint translates messages to SSN's format:

{
  "id": "abc123def456",
  "chatname": "Steve",
  "chatmessage": "Hello world!",
  "chatimg": "https://cdn.example.com/avatar.png",
  "type": "twitch",
  "hasDonation": null,
  "membership": null,
  "moderator": false,
  "bot": false,
  "userid": "user123",
  "textonly": true
}

Message Formats

Our Clean Envelope Format

Used by /stream/:room and /stream/:room/sse:

{
  "id": "abc123def456",
  "type": "chat",
  "room": "my-room-id",
  "user": {
    "id": "user123",
    "name": "Steve",
    "provider": "twitch",
    "role": "viewer",
    "avatar": "https://cdn.example.com/avatar.png"
  },
  "payload": {
    "text": "Hello world!",
    "source": "twitch",
    "chatmessage": "<span>Hello world!</span>",
    "textonly": false
  },
  "ts": 1702000000,
  "meta": {}
}

Field Reference

FieldTypeDescription
idstringUnique message ID
typestringAlways "chat" for stream endpoints
roomstringRoom ID
user.idstringUser's unique ID
user.namestringDisplay name
user.providerstringAuth provider: discord, twitch, youtube, patreon, guest, webhook
user.rolestringUser role: viewer, publisher, mod, bot
user.avatarstring?Avatar URL (optional)
payload.textstringPlain text message content
payload.sourcestring?Original source platform (for SSN messages)
payload.chatmessagestring?HTML-formatted message (for SSN messages)
payload.textonlyboolean?If false, chatmessage contains HTML
tsnumberUnix timestamp (seconds)
metaobjectAdditional metadata

SSE (Server-Sent Events)

The SSE endpoint is a simpler alternative to WebSocket that works in more environments.

Benefits

Usage

const source = new EventSource('https://api.ninjachatter.com/stream/YOUR_ROOM_ID/sse');

source.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  displayMessage(msg.user.name, msg.payload.text, msg.user.avatar);
};

source.onerror = (err) => {
  console.log('Connection error, will auto-reconnect...');
};

Full WebSocket API

For full chat participation (sending messages, reactions, etc.), use the authenticated WebSocket endpoint.

Endpoint

WS /ws/:room?token=JWT_TOKEN

Getting a Token

Use the Ingress API to mint tokens, or use OAuth login flows.

Using embed.js SDK

// Full-featured client with auto-reconnect
const client = ChatEmbed.createChatClient({
  room: 'YOUR_ROOM_ID',
  apiBase: 'https://api.ninjachatter.com',
  wsBase: 'wss://api.ninjachatter.com',
  onMessage: (msg) => {
    console.log(`${msg.user.name}: ${msg.payload.text}`);
  },
  onStateChange: (state) => {
    console.log('Connection state:', state);
  }
});

// Mint a guest token and connect
const token = await ChatEmbed.mintGuestToken({
  room: 'YOUR_ROOM_ID',
  apiBase: 'https://api.ninjachatter.com',
  username: 'Guest User'
});
client.connect(token);

Code Examples

Minimal OBS Overlay

Copy this HTML into an OBS Browser Source:

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      font-family: sans-serif;
      background: transparent;
    }
    #chat {
      display: flex;
      flex-direction: column;
      gap: 8px;
      padding: 16px;
    }
    .msg {
      background: rgba(0,0,0,0.7);
      color: white;
      padding: 8px 12px;
      border-radius: 8px;
      animation: fadeIn 0.3s ease;
    }
    .msg img {
      width: 24px;
      height: 24px;
      border-radius: 50%;
      vertical-align: middle;
      margin-right: 8px;
    }
    .msg .name { font-weight: bold; color: #38bdf8; }
    @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } }
  </style>
</head>
<body>
  <div id="chat"></div>
  <script>
    const ROOM_ID = 'YOUR_ROOM_ID'; // Change this!
    const MAX_MESSAGES = 10;

    const ws = new WebSocket(`wss://api.ninjachatter.com/stream/${ROOM_ID}`);
    const chat = document.getElementById('chat');

    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data);
      const div = document.createElement('div');
      div.className = 'msg';
      div.innerHTML = `
        ${msg.user.avatar ? `<img src="${msg.user.avatar}" alt="">` : ''}
        <span class="name">${msg.user.name}:</span>
        <span>${msg.payload.text}</span>
      `;
      chat.appendChild(div);

      // Remove old messages
      while (chat.children.length > MAX_MESSAGES) {
        chat.firstChild.remove();
      }
    };

    ws.onclose = () => setTimeout(() => location.reload(), 5000);
  </script>
</body>
</html>

React Hook

import { useState, useEffect } from 'react';

function useChatStream(roomId) {
  const [messages, setMessages] = useState([]);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const ws = new WebSocket(`wss://api.ninjachatter.com/stream/${roomId}`);

    ws.onopen = () => setConnected(true);
    ws.onclose = () => setConnected(false);

    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data);
      setMessages(prev => [...prev.slice(-99), msg]);
    };

    return () => ws.close();
  }, [roomId]);

  return { messages, connected };
}

// Usage
function ChatOverlay({ roomId }) {
  const { messages, connected } = useChatStream(roomId);

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.user.name}:</strong> {msg.payload.text}
        </div>
      ))}
    </div>
  );
}

Python Bot

import asyncio
import json
import websockets

async def listen_chat(room_id):
    uri = f"wss://api.ninjachatter.com/stream/{room_id}"

    async with websockets.connect(uri) as ws:
        print(f"Connected to room: {room_id}")

        async for message in ws:
            msg = json.loads(message)
            print(f"{msg['user']['name']}: {msg['payload']['text']}")

# Run the listener
asyncio.run(listen_chat("YOUR_ROOM_ID"))

Related Documentation

Questions? Feedback? GitHub ยท Social Stream Ninja