BlueCallioBlueCallio
Start here

First call in 5 minutes

Two API calls. Two URLs. That's the entire integration.

01

Get your API key

Sign up → create a project → copy your key (starts with bj_live_).

02

Install the SDK

npm install @bluecallio/sdk
03

Create a call from your backend

import BlueCallio from '@bluecallio/sdk';
const bj = new BlueCallio({ apiKey: process.env.BLUECALLIO_API_KEY });

const { callId, callerUrl, receiverUrl } = await bj.createCall({
  callerId: 'user_alice',
  receiverId: 'user_bob',
});
04

Redirect each user — done

Alice opens callerUrl, Bob opens receiverUrl. BlueCallio handles the rest.

Your bj_live_ API key is server-side only. Never send it to the browser.

How it works

Your backend

POST /calls

BlueCallio

Returns 2 URLs

Alice (caller)

Opens callerUrl

Bob (receiver)

Opens receiverUrl

Signaling — WebSocket events between browser and server
WebRTC — peer-to-peer media, negotiated automatically
TURN — relay for calls behind strict firewalls

Three types of credentials

bj_live_...

API Key

Your server → REST API

bj_session_...

Session Token

Browser → WebSocket

JWT Bearer

Dashboard JWT

Dashboard UI → management API

Authentication

Every REST request needs your API key in the Authorization header.

Header
Authorization: Bearer bj_live_your_key_here
Session tokens in callerUrl / receiverUrl are single-use. Do not cache or reuse them.

Hosted UI — zero frontend work

The fastest way to integrate. Create a call from your backend, then redirect your users to a BlueCallio-hosted meeting page. No frontend implementation required.

Ready-made meeting UI
Video & audio calling
Screen sharing
Device selection
Waiting room
Responsive design
Branding support
WebRTC + TURN handled

Integration flow

Your backend

POST /calls

BlueCallio

Returns hostedUrl + tokens

Redirect users

Meeting starts

Branding

Configure branding on your project and the hosted page will apply it automatically.

Project settings (dashboard)
{
  "branding": {
    "companyName": "Acme",
    "logoUrl": "https://cdn.acme.com/logo.png",
    "primaryColor": "#2563EB"
  },
  "theme": "dark",
  "waitingRoom": true
}
Integration time: 5 minutes. Create a call, redirect your users, done.

⚛️ React UI Components

Build a custom interface with reusable React components — no need to implement WebRTC, signaling, or media handling yourself.

install
npm install @bluecallio/react

Quick example

import { MeetingProvider, MeetingRoom, ParticipantGrid, ControlBar } from '@bluecallio/react';

export function Call({ token, callId, signalUrl }) {
  return (
    <MeetingProvider token={token} callId={callId} signalUrl={signalUrl}>
      <MeetingRoom>
        <ParticipantGrid />
      </MeetingRoom>
      <ControlBar />
    </MeetingProvider>
  );
}
MeetingProvider

Context provider — wires the engine, media, and signaling

MeetingRoom

Meeting layout shell with waiting room support

ParticipantGrid / ParticipantTile

Grid of participant video tiles

ActiveSpeakerView

Large speaker view + local PiP

CameraButton / MicrophoneButton

Toggle camera / microphone

ScreenShareButton / LeaveButton

Screen share + end call controls

DeviceSelector

Camera / microphone / speaker picker

WaitingRoom

Waiting room panel

ConnectionStatus

Live connection state indicator

SpeakingIndicator

Active speaker indicator

Hooks

import { useMeeting, useParticipants, useParticipant, useDevices, useConnection } from '@bluecallio/react';

function Status() {
  const { connectionState } = useConnection();
  const participants = useParticipants();
  const { toggleCamera, toggleMicrophone } = useMeeting();
  const devices = useDevices();
  return null;
}
@bluecallio/react is built on top of @bluecallio/sdk — the same session tokens and signaling work across both.

Headless SDK

For developers who want complete control. BlueCallio provides only the communication engine — no UI included.

install
npm install @bluecallio/sdk

Quick example

import { BlueCallioMeeting } from '@bluecallio/sdk';

const meeting = new BlueCallioMeeting({
  token,            // bj_session_... for this participant
  callId,
  signalUrl: 'wss://api.yourdomain.com',
});

await meeting.join();

meeting.camera.enable();
meeting.microphone.disable();
await meeting.screenShare.start();

meeting.on('participant.joined', (p) => console.log('joined', p));
meeting.on('remote.stream', (stream) => attachToVideo(stream));

await meeting.leave();
meeting.join()

Connects socket, authenticates, joins room

meeting.leave()

Ends the meeting, cleans up media

meeting.camera.enable() / disable()

Toggle camera track

meeting.microphone.enable() / disable()

Toggle microphone track

meeting.screenShare.start() / stop()

Start / stop screen share

meeting.participants()

Live list of participants

meeting.connectionState()

Current connection state

meeting.on(event, cb)

Subscribe to meeting events

Events

meeting.on('connected', (p) => {});
meeting.on('disconnected', () => {});
meeting.on('reconnected', () => {});
meeting.on('call.started', (d) => {});
meeting.on('call.ended', (d) => {});
meeting.on('participant.joined', (p) => {});
meeting.on('participant.left', (p) => {});
meeting.on('participant.updated', (p) => {});
meeting.on('camera.enabled' | 'camera.disabled', () => {});
meeting.on('microphone.enabled' | 'microphone.disabled', () => {});
meeting.on('screenShare.started' | 'screenShare.stopped', () => {});
meeting.on('remote.stream', (stream) => {});
meeting.on('remote.stream.ended', () => {});

REST API

Base URL: https://api.yourdomain.com

WebSocket Events

The hosted call UI connects automatically. Build a custom client? Here's the full reference.

connect
import { io } from 'socket.io-client';

const socket = io('https://api.yourdomain.com', {
  auth: { token: 'bj_session_...' },
  transports: ['websocket'],
});

socket.on('connect', () => {
  socket.emit('authenticate', { token: 'bj_session_...' });
});

Connection

connected→ client

Authenticated and joined the meeting. Carries your participant id.

{ participantId: 'user_alice' }
disconnected→ client

Socket dropped.

{}
reconnected→ client

Socket re-established.

{}

Call + participant events

call.started→ both

Call became active.

{ callId: 'clx8f2z...' }
call.ended→ both

Call ended by either side.

{ callId: 'clx8f2z...' }
participant.joined→ others

A participant joined the room.

{ participantId: 'user_bob' }
participant.left→ others

A participant left the room.

{ participantId: 'user_bob' }
participant.updated→ others

Participant media state changed.

{ participantId: 'user_bob', camera: false, microphone: true }
incoming-call→ receiver

Legacy alias — caller is waiting.

{ callId, callerId, type: 'VIDEO' }

Media events

camera.enabled / camera.disabled→ others

Camera toggled.

{ callId }
microphone.enabled / microphone.disabled→ others

Microphone toggled.

{ callId }
screenShare.started / screenShare.stopped→ others

Screen share toggled.

{ callId }

WebRTC signaling

offer / answer / ice-candidate↔ both

WebRTC signaling events relayed by the server.

{ callId, offer? / answer? / candidate? }

Webhooks

BlueCallio POSTs a signed event to your server on every call lifecycle change.

Setup — Dashboard

Open projectWebhook sectionPaste your URLSecret auto-generated
call.created
call.accepted
call.rejected
call.ended

Verify the signature

Always verify the X-BlueCallio-Signature header before processing. Use express.raw() — do not parse JSON first.
import crypto from 'crypto';
import express from 'express';

app.post('/webhooks/bluecallio',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig      = req.headers['x-bluecallio-signature'];
    const expected = 'sha256=' + crypto
      .createHmac('sha256', process.env.BLUECALLIO_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
      return res.status(401).send('invalid signature');
    }

    const event = JSON.parse(req.body);
    // event.event → 'call.created' | 'call.accepted' | ...
    res.json({ ok: true });
  }
);

Examples

Hosted UI — create & redirect (Node.js)

your-server.js
import BlueCallio from '@bluecallio/sdk';

const bj = new BlueCallio({
  apiKey: process.env.BLUECALLIO_API_KEY,
  baseUrl: 'https://api.yourdomain.com',
});

// 1. Create a call from your backend
const { callId, hostedUrl, participants } = await bj.createCall({
  callerId: 'user_alice',
  receiverId: 'user_bob',
  type: 'VIDEO',
});

// 2. Redirect each participant to their hosted page
//    participants[0].hostedUrl  → Alice
//    participants[1].hostedUrl  → Bob
res.redirect(participants[0].hostedUrl);

React Components

import { MeetingProvider, MeetingRoom, ParticipantGrid, CameraButton, MicrophoneButton, ScreenShareButton, LeaveButton, DeviceSelector } from '@bluecallio/react';

export function CustomCall({ token, callId, signalUrl }) {
  return (
    <MeetingProvider token={token} callId={callId} signalUrl={signalUrl}>
      <MeetingRoom>
        <ParticipantGrid />
      </MeetingRoom>
      <div style={{ display: 'flex', gap: 12, justifyContent: 'center', padding: 16 }}>
        <CameraButton />
        <MicrophoneButton />
        <ScreenShareButton />
        <DeviceSelector />
        <LeaveButton />
      </div>
    </MeetingProvider>
  );
}

Headless SDK

import { BlueCallioMeeting } from '@bluecallio/sdk';

const meeting = new BlueCallioMeeting({ token, callId, signalUrl });

meeting.on('remote.stream', (stream) => {
  document.getElementById('remote-video').srcObject = stream;
});

await meeting.join();
meeting.camera.enable();
meeting.microphone.enable();

Error Codes

statusmeaningfix
400Bad RequestCheck request body — missing required field.
401UnauthorizedAPI key or session token is missing, invalid, or expired.
403ForbiddenYou do not own this resource.
404Not FoundcallId does not exist or belongs to another project.
409ConflictCall is already ENDED or REJECTED.
429Rate LimitedSlow down — too many requests per second.
500Server ErrorTemporary. Retry with backoff. Contact support if persistent.

Usage & Billing

BlueCallio is pay-as-you-go. There are no subscriptions and no up-front fees — you pay a simple per-participant-minute rate only for usage beyond the monthly free allowance.

Audio

₹0.20

/ participant-minute

First 500 audio min/month free

Video

₹0.80

/ participant-minute

First 200 video min/month free

Screen share

+₹0.10

/ participant-minute

Always billable, on top of video

How billing works

Start free

Every account gets 500 audio + 200 video minutes/month at no cost. No card required to begin.

Add a payment method

In the dashboard, add a card only when you go to production. You are only charged for minutes beyond the free tier.

Monthly invoice

At the end of each month we generate an invoice for billable usage and auto-charge your saved card. GST of 18% applies on billable usage.

Failed payment

We retry and enter a 7-day grace period. Active calls are never interrupted, but new calls are blocked until payment succeeds.

Screen sharing is always billable (no free allowance). Everything else — Hosted UI, React Components, Headless SDK, REST API, signaling, and the dashboard — is included on the free tier.

❓ FAQ

Which integration should I pick?

Hosted UI for the fastest path (5 minutes). React Components for a branded custom interface without building WebRTC. Headless SDK if you need complete control over the UI.

Can I switch between the three products later?

Yes. All three use the same backend, the same POST /calls response, and the same session tokens. Change the frontend, keep your server-side integration.

Where do I put the API key?

Server-side only (bj_live_...). Never send it to the browser. The hosted page uses per-participant session tokens (bj_session_...), not API keys.

What about calls behind strict firewalls?

BlueCallio provides TURN relay with time-limited credentials. The hosted UI and SDK fetch ICE servers automatically — no configuration needed.

Are session tokens single-use?

Yes. Each token is tied to one participant and one call. Create a new call if you need to re-invite someone.

Do you support group calls?

Currently 1:1 calls. Group calls are on the roadmap.

How do I debug a failed call?

Check the WebSocket connection state, verify the session token matches the correct participant, ensure camera/microphone permissions are granted, and confirm TURN credentials are returned from /turn/credentials.

Still stuck?

Try the playground — make a call in your browser with no code, no API key required.