Web (React) Guide

Build a browser softphone with the jambonz WebRTC SDK

This guide walks you through building a voice calling application in the browser using React.

Step 1: Create a React Project

$npm create vite@latest my-jambonz-app -- --template react-ts
$cd my-jambonz-app

Step 2: Install the SDK

$npm install @jambonz/client-sdk-web

Step 3: Connect to the SBC

Create a component that connects to your jambonz SBC:

1// src/App.tsx
2import { useState } from 'react';
3import { createJambonzClient, ClientState } from '@jambonz/client-sdk-web';
4
5function App() {
6 const [client, setClient] = useState(null);
7 const [state, setState] = useState(ClientState.Disconnected);
8
9 const connect = async () => {
10 const c = createJambonzClient({
11 server: 'wss://sbc.example.com:8443',
12 username: 'your-username',
13 password: 'your-password',
14 });
15
16 c.on('stateChanged', (s) => setState(s));
17 c.on('error', (err) => console.error('Error:', err.message));
18
19 await c.connect();
20 setClient(c);
21 };
22
23 return (
24 <div>
25 <p>Status: {state}</p>
26 <button onClick={connect}>Connect</button>
27 </div>
28 );
29}
30
31export default App;

Run npm run dev and click Connect. You should see the status change to registered.

Step 4: Make an Outbound Call

Add a call button to your component:

1const [call, setCall] = useState(null);
2const [callState, setCallState] = useState(null);
3
4const makeCall = () => {
5 const newCall = client.call('+15551234567');
6
7 newCall.on('stateChanged', (s) => setCallState(s));
8 newCall.on('accepted', () => console.log('Call connected'));
9 newCall.on('ended', (cause) => {
10 console.log('Call ended:', cause.reason);
11 setCall(null);
12 setCallState(null);
13 });
14
15 setCall(newCall);
16};
17
18// In your JSX:
19<button onClick={makeCall} disabled={!client}>Call</button>
20<button onClick={() => call?.hangup()} disabled={!call}>Hang Up</button>
21<p>Call: {callState || 'none'}</p>

Step 5: Handle Incoming Calls

Listen for incoming calls and show an answer/decline UI:

1const [incomingCall, setIncomingCall] = useState(null);
2
3// Add this after creating the client:
4c.on('incoming', (call) => {
5 setIncomingCall(call);
6});
7
8// In your JSX:
9{incomingCall && (
10 <div>
11 <p>Incoming call from: {incomingCall.remoteIdentity}</p>
12 <button onClick={() => {
13 incomingCall.answer();
14 setCall(incomingCall);
15 setIncomingCall(null);
16 }}>Answer</button>
17 <button onClick={() => {
18 incomingCall.hangup();
19 setIncomingCall(null);
20 }}>Decline</button>
21 </div>
22)}

Step 6: Add Call Controls

Add mute, hold, and DTMF:

1const [isMuted, setIsMuted] = useState(false);
2const [isHeld, setIsHeld] = useState(false);
3
4// Listen for events:
5newCall.on('mute', (muted) => setIsMuted(muted));
6newCall.on('hold', (held) => setIsHeld(held));
7
8// In your JSX:
9<button onClick={() => call?.toggleMute()}>
10 {isMuted ? 'Unmute' : 'Mute'}
11</button>
12<button onClick={() => isHeld ? call?.unhold() : call?.hold()}>
13 {isHeld ? 'Resume' : 'Hold'}
14</button>
15<button onClick={() => call?.sendDTMF('1')}>Send 1</button>

Step 7: Using React Hooks (Alternative)

Instead of managing state manually, use the built-in hooks:

1import { useJambonzClient, useCall } from '@jambonz/client-sdk-web';
2
3function Phone() {
4 const client = useJambonzClient({
5 server: 'wss://sbc.example.com:8443',
6 username: 'your-username',
7 password: 'your-password',
8 });
9
10 const call = useCall(client.client);
11
12 return (
13 <div>
14 <p>Status: {client.state}</p>
15 {client.error && <p>Error: {client.error}</p>}
16
17 {!client.isRegistered && (
18 <button onClick={client.connect}>Connect</button>
19 )}
20
21 {client.isRegistered && !call.isActive && (
22 <button onClick={() => call.makeCall('+15551234567')}>Call</button>
23 )}
24
25 {call.isActive && (
26 <div>
27 <p>Call: {call.state}</p>
28 <button onClick={call.toggleMute}>
29 {call.isMuted ? 'Unmute' : 'Mute'}
30 </button>
31 <button onClick={call.toggleHold}>
32 {call.isHeld ? 'Resume' : 'Hold'}
33 </button>
34 <button onClick={call.hangup}>Hang Up</button>
35 </div>
36 )}
37
38 {call.incomingCaller && (
39 <div>
40 <p>Incoming: {call.incomingCaller}</p>
41 <button onClick={call.answerIncoming}>Answer</button>
42 <button onClick={call.declineIncoming}>Decline</button>
43 </div>
44 )}
45 </div>
46 );
47}

Run the Full Example App

The repo includes a complete softphone example built with React + Vite + Tailwind CSS — with a polished dark theme UI, DTMF pad, incoming call handling, and console logs.

$# 1. Clone the repo
$git clone https://github.com/jambonz/webrtc-sdk.git
$cd webrtc-sdk
$
$# 2. Install and build the SDK
$npm install
$npm run build
$
$# 3. Install and run the web example
$cd examples/web
$npm install
$npm run dev

Open http://localhost:5173 in your browser.

What the example includes

The example app has clean separation between SDK logic and UI:

  • src/useJambonz.ts — all SDK interactions (connect, call, mute, hold, transfer, incoming calls). Read this file to learn the SDK.
  • src/App.tsx — wires SDK state to UI components
  • src/components/ — reusable UI: ConnectionForm, DialerView, ActiveCallView, IncomingCallView, DtmfPad, LogPanel

How to use it

  1. Enter your jambonz SBC WebSocket URL (e.g. wss://sbc.example.com:8443)
  2. Enter your SIP username and password
  3. Click Connect — status dot turns green when registered
  4. Enter a number or SIP URI and click Call
  5. Use the in-call controls: mute, hold, DTMF pad, hang up
  6. Incoming calls show an answer/decline screen
  7. Expand Console Logs at the bottom to see SDK events

Source: github.com/jambonz/webrtc-sdk/tree/main/examples/web