Auth & tokens

How your server signs the JWTs that your clients use to connect to Kardolive.

1. One-time setup

  1. Sign up at https://kardocloud.com/app/signup (or use the SSO bridge when available).
  2. Create a Project at /app/projects. One project per environment is the norm:
    • your-app-dev
    • your-app-staging
    • your-app-prod
  3. Toggle on the features you'll use: Chat, Live streaming, Conferencing, Voice/Video call, Recording, Transcription, Webhooks.
  4. Open the project → API keys → mint a key with scopes:
    • chat:token — mint chat JWTs
    • channels:read + channels:write — manage channels
    • streams:read + streams:write — manage live streams
    • recordings:read + recordings:write
    • webhooks:read + webhooks:write
  5. Store the kl_live_* key in your secrets store. Treat it like a database password.

Security boundary: the kl_live_* key authenticates your server to Kardolive. End-user clients never see this key. They use short-lived JWTs your server mints.

2. The full integration flow

┌──────────────┐   1. user logs into your app       ┌────────────────┐
│              │ ──────────────────────────────▶   │                │
│   your app   │                                    │   your app     │
│   client     │   2. ask for chat/video token      │   backend      │
│  (browser/   │ ──────────────────────────────▶   │  (NestJS, etc) │
│   mobile)    │                                    │                │
│              │                                    │  HOLDS         │
│              │                                    │  kl_live_xxx   │
│              │   4. { token, chatUrl, sfuUrl }   │                │
│              │ ◀──────────────────────────────   │                │
└──────┬───────┘                                    └────────┬───────┘
       │                                                     │
       │ 5. open WebSocket with token                        │ 3. POST /v1/me/channels/token
       │                                                     │    with kl_live_xxx
       │                                                     ▼
       │                                                ┌─────────────────┐
       │ 6. real-time traffic                           │                 │
       └──────────────────────────────────────────────▶│   Kardolive     │
                                                       │   API + chat WS │
                                                       │   + mediasoup   │
                                                       └─────────────────┘

Two things to notice:

3. Server-side: mint user tokens

your backend exposes one endpoint per surface that exchanges the long-lived API key for a short-lived user JWT. The user's identity is whatever your app already knows about them.

NestJS — chat token endpoint

// your-app/apps/backend/src/kardolive/kardolive.controller.ts
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtAuthGuard } from '../auth/jwt.guard';

@Controller('kardolive')
@UseGuards(JwtAuthGuard)
export class KardoliveController {
  constructor(private readonly config: ConfigService) {}

  @Post('chat-token')
  async chatToken(
    @Req() req: { user: { id: string; name?: string } },
    @Body() body: { channelId?: string },
  ) {
    const res = await fetch('https://kardocloud.com/v1/me/channels/token', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${this.config.get('KARDOLIVE_API_KEY')}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        userId: req.user.id,           // your users ID
        displayName: req.user.name,
        channelId: body.channelId,     // optional: scope to one channel
      }),
    });
    if (!res.ok) throw new Error(`Kardolive token mint failed: ${res.status}`);
    return res.json(); // { token, chatUrl, expiresAt }
  }
}

NestJS — session token endpoint (covers live, conference, call)

// your-app/apps/backend/src/kardolive/kardolive.controller.ts
@Post('session-token')
async sessionToken(
  @Req() req: { user: { id: string; name?: string } },
  @Body() body: { sessionId: string; role?: 'host' | 'cohost' | 'guest' | 'viewer' },
) {
  const res = await fetch(
    `https://kardocloud.com/v1/me/sessions/${body.sessionId}/token`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${this.config.get('KARDOLIVE_API_KEY')}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        userId: req.user.id,
        displayName: req.user.name,
        role: body.role ?? 'guest',
      }),
    },
  );
  if (!res.ok) throw new Error(`Kardolive session token failed: ${res.status}`);
  return res.json();
  // returns:
  // { token, sfuUrl, chatUrl, sessionId, role,
  //   perms: { audio: bool, video: bool, screen: bool },
  //   waitingRoom: bool, expiresAt }
}

Plain Node.js — same idea, no framework

// Express / Fastify / raw Node
app.post('/api/kardolive/chat-token', async (req, res) => {
  const r = await fetch('https://kardocloud.com/v1/me/channels/token', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.KARDOLIVE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId: req.user.id,
      displayName: req.user.name,
      channelId: req.body.channelId,
    }),
  });
  res.status(r.status).json(await r.json());
});

4. Client-side: use the token

The client never sees kl_live_*. It only ever sees its own short-lived JWT.

React / browser — chat connection

// 1) fetch a token from your server
const tokRes = await fetch('/api/kardolive/chat-token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ channelId: 'chn_abc123' }),
});
const { token, chatUrl } = await tokRes.json();

// 2) open the chat WebSocket
const ws = new WebSocket(
  `${chatUrl}/v1/chat/socket?room=chn_abc123&token=${token}`,
);

// 3) send & receive
ws.onopen = () =>
  ws.send(JSON.stringify({ type: 'chat.send', data: { body: 'hi' } }));
ws.onmessage = (e) => console.log(JSON.parse(e.data));

See Chat → for a full React component that wraps this.

5. Security: what to put where

TokenLifetimeLives onUsed by
kl_live_* API key Until you revoke your server (env var / secrets manager) Server → Kardolive REST API only
User JWT (chat / session / SFU) 1 hour Mint per-request, return to client Client WebSocket connections

Common mistakes:

  • Putting kl_live_* in client-side JS — anyone can scrape your bundle and use your quota.
  • Reusing one JWT across many users — every user should get their own JWT with their own userId claim, so analytics and per-user moderation work.
  • Storing JWTs in localStorage long-term — they expire in 1 hour. Mint fresh each session.

Next

You've got auth wired. Pick your client surface:
React quickstart → · Vanilla JS → · Chat →