03 โ Node.js backend integration
Your Node backend is responsible for authenticating your users and then
minting LiveKit tokens for them via the arzbridge API. Below is a clean,
production-ready setup (no extra SDK needed โ just fetch, built into Node 18+).
1. Configuration
Keep credentials in environment variables โ never in code or the frontend.
# .env
ARZBRIDGE_BASE_URL=https://calls.arzbridge.com/api/v1
ARZBRIDGE_API_KEY=YOUR_API_KEY
ARZBRIDGE_API_SECRET=YOUR_API_SECRET
2. A reusable client (arzbridge.js)
// arzbridge.js โ a tiny, dependency-free client for the arzbridge calls API
const BASE = process.env.ARZBRIDGE_BASE_URL;
const AUTH = `Bearer ${process.env.ARZBRIDGE_API_KEY}:${process.env.ARZBRIDGE_API_SECRET}`;
async function call(method, path, body) {
const res = await fetch(`${BASE}${path}`, {
method,
headers: { Authorization: AUTH, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const msg = json?.error?.message || `arzbridge ${res.status}`;
const err = new Error(msg);
err.status = res.status;
err.body = json;
throw err;
}
return json.data;
}
export const arzbridge = {
// Mint a join token for a participant.
createToken: (opts) => call('POST', '/tokens', opts),
// Rooms
listRooms: () => call('GET', '/rooms'),
createRoom: (opts) => call('POST', '/rooms', opts),
getRoom: (name) => call('GET', `/rooms/${encodeURIComponent(name)}`),
deleteRoom: (name) => call('DELETE', `/rooms/${encodeURIComponent(name)}`),
// Participants
listParticipants: (room) => call('GET', `/rooms/${encodeURIComponent(room)}/participants`),
removeParticipant: (room, identity) =>
call('DELETE', `/rooms/${encodeURIComponent(room)}/participants/${encodeURIComponent(identity)}`),
// Data message
sendData: (room, data, opts = {}) =>
call('POST', `/rooms/${encodeURIComponent(room)}/data`, { data, ...opts }),
};
3. Expose a token endpoint to your app (Express)
// server.js
import express from 'express';
import { arzbridge } from './arzbridge.js';
const app = express();
app.use(express.json());
// Your app calls THIS endpoint (with your own auth) to join a call.
app.post('/api/calls/join', async (req, res) => {
try {
// 1) Authenticate the user with YOUR system (session, JWT, etc.)
const user = req.user; // however you auth
if (!user) return res.status(401).json({ error: 'unauthenticated' });
// 2) Decide which room they may join + their role (your business rules)
const { roomId } = req.body;
// 3) Mint a token scoped to that user + room
const { token, ws_url } = await arzbridge.createToken({
room: `room-${roomId}`,
identity: `user-${user.id}`,
name: user.name,
ttl: 3600,
// role example: a viewer in a webinar
// can_publish: false,
metadata: { role: user.role },
});
// 4) Hand the token to your app
res.json({ token, wsUrl: ws_url });
} catch (e) {
res.status(e.status || 500).json({ error: e.message });
}
});
app.listen(3000, () => console.log('listening on :3000'));
Your Flutter/web app calls POST /api/calls/join and gets back { token, wsUrl }.
4. Server-side controls
// Kick a user
await arzbridge.removeParticipant('room-42', 'user-99');
// End a call for everyone
await arzbridge.deleteRoom('room-42');
// Broadcast a signal (e.g. "quiz started") to all participants
await arzbridge.sendData('room-42', JSON.stringify({ type: 'quiz_started' }), { topic: 'signals' });
// See who's connected
const people = await arzbridge.listParticipants('room-42');
5. Error handling & retries
401/403โ fix credentials/scopes (config issue, not transient).422โ bad input; inspecterr.body.error.fields.429โ you're rate-limited; honorRetry-Afterand back off.5xxโ retry with exponential backoff (2โ3 tries).
async function withRetry(fn, tries = 3) {
for (let i = 0; i < tries; i++) {
try { return await fn(); }
catch (e) {
if (e.status && e.status < 500 && e.status !== 429) throw e; // don't retry client errors
if (i === tries - 1) throw e;
await new Promise(r => setTimeout(r, 300 * 2 ** i));
}
}
}
(Optional) Node as a call client
If you also have a web frontend, use the browser SDK livekit-client with the
token from your backend โ see the snippet in the
main README and
LiveKit JS docs. For mobile, see
Flutter.