╔══════════════════════════════════════════════════════════════════╗
║ ║
║ ██╗ ██╗███████╗██████╗ ██╗ ██╗███████╗ ║
║ ██║ ██║██╔════╝██╔══██╗██║ ██║██╔════╝ ║
║ ██║ ██║█████╗ ██████╔╝██║ ██║█████╗ ║
║ ╚██╗ ██╔╝██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ║
║ ╚████╔╝ ███████╗██║ ██║ ╚████╔╝ ███████╗ ║
║ ╚═══╝ ╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝ ║
║ ║
║ Video calls that don't suck. An SDK that finally works. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
Crystal-clear video calls · Multi-tenant embeds · Developer SDK
Built with ❤️ by Saksham Singhal
Verve started as a private, self-hosted video calling platform — like Zoom met WebRTC at a hackathon and had a baby that actually works.
It has since evolved into a hybrid communication SDK platform. You can embed Verve directly into any website via an <iframe>, or integrate it fully into any application using the Verve JavaScript SDK and prebuilt React components. All while keeping Mediasoup/WebRTC internals completely hidden from consumers.
Verve uses a Selective Forwarding Unit (SFU) architecture via Mediasoup — streams are intelligently routed through the server instead of P2P spaghetti. Translation: it scales, it's fast, and it doesn't melt your CPU.
Pick everything. It's all-you-can-eat.
| Feature | What it does |
|---|---|
| 📹 Video & Audio | HD video (720p) + crystal-clear audio with echo cancellation & noise suppression |
| 🏠 Room System | Create or join rooms with unique IDs. Share the code, start the party |
| 💬 In-Call Chat | Real-time messaging with unread badges, auto-scroll, and chat history (last 200 msgs) |
| 🖥️ Screen Sharing | One-click screen share. Auto-stops when you hit the browser's native stop button |
| 👥 Participants Panel | See who's in the room at a glance |
| ✋ Raise Hand | Toggle raise hand with animated badge. Auto-lowers after 30s |
| 🎙️ Media Controls | Mute/unmute mic, toggle camera on/off, leave meeting gracefully |
| 📐 Adaptive Grid | Smart video layout that adapts for 1, 2, 4, or many participants |
| 🌙 Dark Theme | Sleek dark UI. Your eyes will thank you at 2 AM |
| 🔐 Authentication | JWT-based auth with bcrypt password hashing. Register, login, stay secure |
| 🎭 Reactions | Send floating emoji reactions during calls |
| ⏱️ Focus Timer | Host-controlled countdown timer visible to all participants |
| 📌 Pin to Focus | Pin any participant's stream to full-screen focus view |
| 📊 Speaking Stats | Track how long each participant has spoken |
| Feature | What it does |
|---|---|
| 🏢 Multi-Tenant | Isolated rooms per tenant — guests from one tenant can never access another's rooms |
| 🎨 Dynamic Branding | Tenants configure logo & primary color; embed view applies them automatically via CSS vars |
| 🔑 Embed Tokens | Versioned, scoped JWTs (v1) bind a guest to a specific tenant + room |
| 🔒 Origin Protection | allowedOrigins enforcement — only whitelisted domains can embed your rooms |
| ♻️ Auto Room Expiry | MongoDB TTL index auto-deletes rooms after their configured lifetime |
| 🪟 iframe Support | Embed any tenant room with a single <iframe> tag |
| Feature | What it does |
|---|---|
| 📦 npm-first SDK | Verve.init() → clean session object. No Mediasoup knowledge required |
| 🤝 State Machine | Connection lifecycle: idle → connecting → connected → reconnecting → disconnected → destroyed |
| 🎯 Stable Events | Namespaced event contract: participant.joined, chat.message, room.ended, etc. |
| 🧩 Prebuilt Components | VerveRoom, VerveControls, VerveChat, VerveParticipants — ready to drop in |
| 🎨 CSS Variable Theming | Override --verve-primary, --verve-bg, --verve-text etc. on .verve-root |
| ⚙️ Room Config | Disable chat, camera, or screen share per session via config object |
| 🔒 Framework Agnostic Core | VerveSession has zero React imports — usable in Vue, Svelte, vanilla JS, or anywhere |
╭──────────────────────────────────╮
│ THE INTERNET │
╰──────────┬───────────┬───────────╯
│ │
┌──────────▼──┐ ┌────▼──────────┐ ┌──────────────────┐
│ Browser A │ │ Browser B │ │ 3rd-party App │
│ (React) │ │ (React) │ │ (SDK / iframe) │
└──────┬──────┘ └──────┬────────┘ └────────┬─────────┘
│ │ │
produce & produce & Verve SDK / iframe
consume consume │
│ │ │
┌──────▼─────────────────▼─────────────────────▼──────┐
│ │
│ 🚀 Verve Server (Node.js) │
│ │
│ ┌─────────────────┐ ┌───────────────────────┐ │
│ │ Mediasoup SFU │ │ Tenant API │ │
│ │ (WebRTC/RTP) │ │ POST /rooms │ │
│ └─────────────────┘ │ POST /rooms/:id/token│ │
│ │ GET /branding/:id │ │
│ ┌─────────────────┐ └───────────────────────┘ │
│ │ Socket.io │ │
│ │ Signaling + │ ┌───────────────────────┐ │
│ │ Chat + Raise │ │ JWT Auth Middleware │ │
│ │ Hand + Embed │ │ (DB users + guests) │ │
│ └─────────────────┘ └───────────────────────┘ │
└───────────────────────────────┬───────────────────────┘
│
┌────────▼────────┐
│ MongoDB Atlas │
│ Users, Tenants │
│ Rooms (TTL) │
└─────────────────┘
Developer Code
│
├── Path 1: iframe embed (Phase 1)
│ <iframe src="/embed/ROOM?token=JWT" />
│
├── Path 2: SDK headless (Phase 2)
│ const session = await Verve.init({ apiKey, roomId, user })
│ await session.joinRoom()
│ session.on("participant.joined", handler)
│
└── Path 3: SDK prebuilt React (Phase 2)
<VerveProvider session={session}>
<VerveRoom />
</VerveProvider>
All three paths use the same backend, same socket events, same Mediasoup stack.
Vanilla SDK Core (framework-agnostic)
↓
React Bindings / Prebuilt Components
↓
Existing RTC Core (untouched)
🧠 How does the SFU magic work?
Instead of every participant sending their video to every other participant (which is O(n²) chaos), Verve uses a Selective Forwarding Unit:
- You produce → Your camera/mic stream goes to the server (just once!)
- Server routes → Mediasoup intelligently forwards your stream to everyone else
- They consume → Other peers receive your stream from the server
Without SFU (Mesh — yikes): With SFU (Verve — chef's kiss):
A ←→ B A → Server → B
A ←→ C A → Server → C
Each client: N uploads!!! 🔥💀 Each client: 1 upload, N downloads
Server handles the routing 🎯
| Layer | Tech | Why? |
| 🎨 Frontend | React 19 + Vite 7 | Blazing fast HMR, modern React with hooks |
| 🎭 Styling | Vanilla CSS + CSS Variables | Pure hand-crafted CSS, SDK theming via custom properties |
| ⚙️ Backend | Node.js + Express 5 | Battle-tested, async-first, runs everywhere |
| 📡 Real-time | Socket.io 4 | WebSocket with fallbacks, rooms, and ack support |
| 📹 Media | Mediasoup 3 (SFU) | Enterprise-grade WebRTC SFU. Handles the hard stuff |
| 🗄️ Database | MongoDB Atlas | Cloud-native document store; TTL indexes for room cleanup |
| 🔐 Auth | JWT + bcrypt | Stateless tokens (versioned v1) + salted password hashing |
| 🧭 Routing | React Router 7 | Client-side navigation with /room, /embed, and dynamic routes |
| 📦 SDK | Vanilla JS (framework-agnostic) | Works in React, Vue, Svelte, or plain JS apps |
Time to first call: ~3 minutes ⏱️
- Node.js 18+ — Download here
- npm — Comes with Node.js
- MongoDB Atlas account — Free tier
git clone https://github.com/SakshamSinghal20/Verve.git
cd Verve
# Backend
cd Backend && npm install
# Frontend
cd ../Frontend/Live-calling && npm install📋 Backend (.env) — click to expand
cd Backend
cp .env.example .envEdit Backend/.env:
| Variable | Default | What it does |
|---|---|---|
PORT |
5000 |
Server port |
FRONTEND_URL |
https://verve-live.vercel.app |
CORS origin |
SERVER_IP |
127.0.0.1 |
Mediasoup announced IP |
MONGO_URI |
— | MongoDB connection string |
JWT_SECRET |
verve-dev-secret-change-me |
Signs all JWTs (user & embed) |
📋 Frontend (.env) — click to expand
cd Frontend/Live-calling
cp .env.example .envEdit Frontend/Live-calling/.env:
| Variable | Default | What it does |
|---|---|---|
VITE_BACKEND_URL |
http://3.108.133.255:5000 |
Backend URL |
# Terminal 1 — Backend
cd Backend && node server.js
# Terminal 2 — Frontend
cd Frontend/Live-calling && npm run dev- Open https://verve-live.vercel.app in your browser
- Register an account, then click ✦ Create Meeting
- Share the Room ID — they paste it in Join and you're live 🎉
💡 Open two browser tabs to test locally.
Verve/
│
├── 📂 Backend/
│ ├── 🚀 server.js ← SFU + Socket.io + Tenant isolation
│ ├── 📂 middleware/
│ │ └── 🔐 auth.js ← JWT verification (DB users + embed guests)
│ ├── 📂 models/
│ │ ├── 👤 User.js ← MongoDB user schema
│ │ ├── 🏢 Tenant.js ← Multi-tenant schema + API key generator
│ │ └── 🚪 Room.js ← Room schema with tenantId + TTL expiry
│ ├── 📂 routes/
│ │ ├── 🛣️ auth.js ← Register & login endpoints
│ │ ├── 🚪 rooms.js ← Room API: instant, custom, join
│ │ └── 🏢 tenant.js ← Tenant API: rooms, tokens, branding
│ └── 📋 .env.example
│
├── 📂 Frontend/
│ └── 📂 Live-calling/
│ └── 📂 src/
│ ├── 🏠 App.jsx ← Landing page
│ ├── ⚡ main.jsx ← Router: /room, /embed, /login, /register
│ ├── 🔌 socket.js ← Socket factory (supports embed tokens)
│ ├── 📂 context/
│ │ ├── AuthContext.jsx ← User session context
│ │ └── EmbedContext.jsx ← Embed mode + tenant context
│ ├── 📂 hooks/
│ │ └── 🎣 useRoomSocket.js ← All Mediasoup + socket logic
│ ├── 📂 pages/
│ │ ├── 📹 Room.jsx ← Standard meeting room
│ │ ├── 🪟 EmbedRoom.jsx ← Branded iframe-friendly room
│ │ ├── 🎨 EmbedRoom.css ← Embed-specific styles
│ │ ├── 🔑 Login.jsx
│ │ └── 📝 Register.jsx
│ ├── 📂 components/
│ │ ├── VideoGrid.jsx ← Adaptive video layout
│ │ ├── ControlBar.jsx ← Mic, cam, screen, chat controls
│ │ ├── ChatPanel.jsx ← Real-time chat panel
│ │ ├── ParticipantsPanel.jsx
│ │ ├── SpeakingStatsPanel.jsx
│ │ ├── TimerBar.jsx
│ │ ├── ReactionOverlay.jsx
│ │ └── Icons.jsx
│ ├── 📂 sdk/ ← 📦 Phase 2: Verve Communication SDK
│ │ ├── index.js ← Barrel export (single import path)
│ │ ├── VerveSDK.js ← Verve.init() entry point
│ │ ├── VerveSession.js ← State machine + event translation
│ │ ├── VerveEventEmitter.js ← Namespaced event system
│ │ ├── VerveProvider.jsx ← React context + theme application
│ │ ├── VerveTheme.css ← CSS variable theming layer
│ │ ├── VerveRoom.jsx ← Full prebuilt room component
│ │ ├── VerveControls.jsx ← Standalone control bar
│ │ ├── VerveChat.jsx ← Standalone chat panel
│ │ └── VerveParticipants.jsx ← Standalone participants panel
│ └── 📂 utils/
│ └── decodeJwt.js ← Client-side JWT decoder (UI only)
│
└── 📖 README.md
The Tenant API lets any third-party service embed Verve rooms with full branding isolation. All RTC logic is reused — no parallel engine.
POST /api/tenant/register
Content-Type: application/json
{
"name": "Acme Corp",
"primaryColor": "#ff6b00",
"allowedOrigins": ["https://acme.com"]
}Response:
{
"tenantId": "...",
"apiKey": "vk_...",
"primaryColor": "#ff6b00"
}POST /api/tenant/rooms
x-api-key: vk_...
{ "ttlHours": 2 }Response:
{
"roomId": "abc123def456",
"embedUrl": "https://verve-live.vercel.app/embed/abc123def456",
"expiresAt": "2024-01-01T14:00:00.000Z"
}POST /api/tenant/rooms/:roomId/token
x-api-key: vk_...
{ "guestName": "Alice", "role": "participant" }Response:
{
"token": "eyJ...",
"embedUrl": "https://verve-live.vercel.app/embed/ROOM?token=eyJ...",
"guestId": "guest_abc123"
}<iframe
src="https://verve-live.vercel.app/embed/ROOM_ID?token=JWT_TOKEN"
allow="camera; microphone; display-capture; fullscreen; clipboard-write"
width="100%"
height="640px"
/>The embedded iframe communicates status changes back to the host window using window.parent.postMessage. The host page can listen for these events to track the meeting session state:
window.addEventListener("message", (event) => {
// Ensure the message is from your Verve deployment origin
if (event.origin !== "https://verve-live.vercel.app") return;
const data = event.data;
if (data?.type === "verve:status") {
const { status, roomId, tenantId, role, guestId, guestName, roomEnded } = data;
console.log(`Room Status changed to: ${status} (Ended: ${roomEnded})`);
}
});Verve handles all selective forwarding unit (SFU) routing, WebRTC peer connection transport, DTLS handshakes, and multi-tenant database isolation. Implementing raw WebRTC or Mediasoup requires managing complex connection states, SDP signaling, and media streams; Verve wraps all this logic inside a simple, unified interface.
The SDK is organized into three architectural layers:
- Vanilla SDK Core (Framework Agnostic):
VerveSDK(entry point),VerveSession(connection state machine), andVerveEventEmitter(stable events). Has zero dependencies on UI frameworks and can be used in React, Vue, Svelte, or vanilla JS. - React Bindings:
VerveProvider(injects theme CSS variables and provides context) anduseVerveSession(hook). - Prebuilt UI Components: Ready-made, highly functional React components (
VerveRoom,VerveControls,VerveChat,VerveParticipants) that plug directly into the session.
For partner platforms that already receive a server-generated embed token, mount Verve without React:
import Verve from "./sdk";
const embed = Verve.embed({
container: "#support-call",
roomId: "abc123def456",
token: "eyJ...",
frontendUrl: "https://your-verve-host.com",
style: { minHeight: "640px", borderRadius: "12px" },
});
// Later:
embed.destroy();You can also pass the full embedUrl returned by POST /api/tenant/rooms/:roomId/token.
Keep tenant API keys on your server; the browser embed only needs the short-lived token.
The SDK provides a clean, high-level API for code-based integration. Mediasoup/WebRTC internals stay completely hidden.
# Import directly from the src/sdk path (monorepo setup)
import Verve from "./sdk";const session = await Verve.init({
apiKey: "vk_...",
roomId: "abc123def456",
user: { name: "Alice", role: "participant" },
config: {
chatEnabled: true,
screenShareEnabled: true,
cameraEnabled: true,
},
theme: {
primary: "#7c3aed",
bg: "#0a0a0f",
text: "#ffffff",
},
});
await session.joinRoom();session.toggleMic(); // mute / unmute
session.toggleCamera(); // camera on / off
session.startScreenShare(); // start sharing
session.stopScreenShare(); // stop sharing
session.sendMessage("hello"); // send chat message
session.toggleRaiseHand(); // raise / lower hand
session.leaveRoom(); // disconnect
session.destroy(); // full cleanupsession.on("room.connected", (data) => { /* ... */ });
session.on("room.disconnected", (data) => { /* ... */ });
session.on("room.ended", (data) => { /* ... */ });
session.on("participant.joined", (data) => { /* id, name */ });
session.on("participant.left", (data) => { /* id, name */ });
session.on("chat.message", (msg) => { /* ... */ });
session.on("mic.changed", (data) => { /* ... */ });
session.on("camera.changed", (data) => { /* ... */ });
session.on("screenShare.started", (data) => { /* ... */ });
session.on("screenShare.stopped", (data) => { /* ... */ });
session.on("room.stateChanged", ({ from, to }) => { /* state machine */ });idle → connecting → connected → reconnecting → disconnected → destroyed
console.log(session.connectionState); // "idle" | "connecting" | "connected" | ...
console.log(session.connected); // boolean shorthandimport { VerveProvider, VerveRoom } from "./sdk";
function App() {
return (
<VerveProvider session={session}>
<VerveRoom />
</VerveProvider>
);
}Override CSS variables on .verve-root or pass theme to Verve.init():
.verve-root {
--verve-primary: #7c3aed;
--verve-bg: #0a0a0f;
--verve-text: #ffffff;
--verve-border: rgba(255, 255, 255, 0.08);
--verve-font: 'Inter', sans-serif;
--verve-radius: 12px;
}📡 Click to see all Socket.io events
| Event | Payload | Response/Callback | Description |
|---|---|---|---|
join-room |
rawRoomId (string) |
{ rtpCapabilities, isCreator, myUserId, myName, timerState } or { error } |
Normalizes Room ID, validates tokens (regular or embed), verifies expiration, and returns room details. |
create-send-transport |
— | { id, iceParameters, iceCandidates, dtlsParameters } or { error } |
Initiates worker send transport on the server. |
create-recv-transport |
— | { id, iceParameters, iceCandidates, dtlsParameters } or { error } |
Initiates worker receive transport on the server. |
connect-transport |
{ transportId, dtlsParameters } |
{ connected: true } or { error } |
Completes DTLS handshakes for the transport. |
produce |
{ transportId, kind, rtpParameters, appData } |
{ id } or { error } |
Starts producing video, audio, or screen media. |
consume |
{ producerId, rtpCapabilities } |
{ id, producerId, kind, rtpParameters } or { error } |
Creates a paused stream consumer for a participant. |
resume-consumer |
{ consumerId } |
{ resumed: true } or { error } |
Resumes the paused stream consumer. |
get-producers |
— | { producers: [] } (list of active producers) or { error } |
Returns all active stream producers in the room. |
send-message |
{ message } |
{ sent: true } or { error } |
Sends a message to the room chat channel. |
get-chat-history |
— | { messages: [] } or { error } |
Returns the recent in-memory chat message history. |
raise-hand |
{ raised } |
— | Toggles hand-raised status; broadcasts state to the room. |
send-reaction |
{ type } |
— | Sends floating reactions ("confetti", "clap", "laugh", "heart", "fire", "thumbsup"). Rate-limited to 1 reaction per second. |
speaking-update |
{ durationMs } |
— | Reports active speaking duration for dynamic statistics tracking. |
start-timer |
{ durationMs } |
— | Starts the shared focus timer (host only). |
stop-timer |
— | — | Stops the active focus timer (host only). |
💡 SDK Client Actions: The vanilla SDK wrapper (
VerveSession.js) also exposes methods that emit helper events liketoggle-mute,toggle-camera,start-screen-share,stop-screen-share,toggle-raise-hand, andchat-messageto assist with headless custom integrations.
| Event | Payload | Trigger |
|---|---|---|
new-producer |
{ producerId, peerId, userId, name, kind, appData } |
Broadcasted when a participant begins producing media. |
new-peer |
{ peerId, userId, name } |
Broadcasted when a new user successfully joins the room. |
peer-left |
{ peerId, userId } |
Broadcasted when a participant leaves or disconnects. |
producer-closed |
{ producerId } |
Broadcasted when a producer shuts down their media track. |
peers-list |
{ peers: [{ peerId, userId, name }] } |
Emitted to update the local participants roster. |
new-message |
{ peerId, userId, name, message, timestamp } |
Dispatched to deliver a new chat message to all room members. |
hand-raised |
{ peerId, userId, name, raised } |
Dispatched when a participant raises or lowers their hand. |
reaction |
{ type, peerId, userId, name, timestamp } |
Dispatched when a participant triggers an emoji reaction. |
speaking-stats |
{ stats: { [userId]: { ms, name } } } |
Periodic broadcast tracking cumulative speaking durations. |
timer-sync |
{ durationMs, startedAt } |
Dispatched to sync focus timer configuration across all peers. |
timer-ended |
— | Dispatched when the active focus timer duration elapses. |
room-closed |
{ reason } |
Sent when the room host closes the meeting or disconnects. |
| Method | Endpoint | Body | Auth? |
|---|---|---|---|
POST |
/api/auth/register |
{ name, email, password } |
❌ |
POST |
/api/auth/login |
{ email, password } |
❌ |
GET |
/api/auth/me |
— | ✅ Bearer Token |
| Method | Endpoint | Body | Auth? |
|---|---|---|---|
POST |
/api/rooms/instant |
— | ✅ Bearer Token |
POST |
/api/rooms |
{ roomId } |
✅ Bearer Token |
POST |
/api/rooms/join |
{ roomId } |
✅ Bearer Token |
| Method | Endpoint | Body | Auth? |
|---|---|---|---|
POST |
/api/tenant/register |
{ name, primaryColor, allowedOrigins } |
❌ |
POST |
/api/tenant/rooms |
{ ttlHours } |
✅ x-api-key |
POST |
/api/tenant/rooms/:roomId/token |
{ guestName, role } |
✅ x-api-key |
GET |
/api/tenant/branding/:tenantId |
— | ❌ (public) |
PUT |
/api/tenant/branding |
{ name, logo, primaryColor, allowedOrigins } |
✅ x-api-key |
- Core video & audio calling with SFU
- Room system with unique IDs
- In-call chat with history
- Screen sharing
- Raise hand feature
- Adaptive grid layout
- Dark theme UI
- User authentication (JWT + MongoDB)
- Speaking stats panel
- Floating emoji reactions
- Focus timer (host-controlled)
- Pin-to-focus participant view
- Phase 1: Multi-tenant embed system with branded iframes
- Phase 1: Scoped JWT embed tokens with TTL room expiry
- Phase 1: Origin-validated allowedOrigins for iframe security
- Phase 2: Hybrid Communication SDK (Verve.init)
- Phase 2: Connection state machine
- Phase 2: Namespaced event system
- Phase 2: Prebuilt React components (VerveRoom, VerveChat, etc.)
- Phase 2: CSS variable theming layer
- Recording support
- Virtual backgrounds
- Breakout rooms
- Mobile-responsive design
- E2E encryption
- Fork the repo
- Create your feature branch (
git checkout -b feature/amazing-thing) - Commit your changes (
git commit -m 'Add amazing thing') - Push to the branch (
git push origin feature/amazing-thing) - Open a Pull Request
This project is available under the MIT License.