React Native Guide

Build an iOS + Android softphone with the jambonz WebRTC SDK

This guide walks you through building a voice calling app for iOS and Android using React Native.

The SDK works on both simulators/emulators and physical devices. A physical device is recommended for real call testing with audio.

Step 1: Create a React Native Project

$npx @react-native-community/cli init MyJambonzApp
$cd MyJambonzApp

Step 2: Install the SDK

$npm install @jambonz/client-sdk-react-native react-native-webrtc

For iOS:

$cd ios && pod install && cd ..

Step 3: Android Permissions

Add to android/app/src/main/AndroidManifest.xml inside <manifest>:

1<uses-permission android:name="android.permission.INTERNET" />
2<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
3<uses-permission android:name="android.permission.RECORD_AUDIO" />
4<uses-permission android:name="android.permission.CAMERA" />
5<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Step 4: iOS Permissions

Add to ios/MyJambonzApp/Info.plist:

1<key>NSMicrophoneUsageDescription</key>
2<string>Required for voice calls</string>

Step 5: Connect to the SBC

1// App.tsx
2import React, { useState } from 'react';
3import { View, Text, TextInput, Pressable } from 'react-native';
4import {
5 createJambonzClient,
6 JambonzClient,
7 ClientState,
8} from '@jambonz/client-sdk-react-native';
9
10function App() {
11 const [client, setClient] = useState<JambonzClient | null>(null);
12 const [state, setState] = useState<ClientState>(ClientState.Disconnected);
13
14 const connect = async () => {
15 const c = createJambonzClient({
16 server: 'wss://sbc.example.com:8443',
17 username: 'your-username',
18 password: 'your-password',
19 });
20
21 c.on('stateChanged', (s) => setState(s));
22 c.on('error', (err) => console.log('Error:', err.message));
23
24 await c.connect();
25 setClient(c);
26 };
27
28 return (
29 <View style={{ padding: 40 }}>
30 <Text>Status: {state}</Text>
31 <Pressable onPress={connect}>
32 <Text>Connect</Text>
33 </Pressable>
34 </View>
35 );
36}
37
38export default App;

Run on your device:

$# Android
$npx react-native run-android
$
$# iOS (physical device only)
$npx react-native run-ios --device

Step 6: Make an Outbound Call

1const [call, setCall] = useState(null);
2const [callState, setCallState] = useState(null);
3
4const makeCall = (target: string) => {
5 if (!client) return;
6
7 const newCall = client.call(target);
8 newCall.on('stateChanged', (s) => setCallState(s));
9 newCall.on('ended', () => {
10 setCall(null);
11 setCallState(null);
12 });
13 newCall.on('failed', () => {
14 setCall(null);
15 setCallState(null);
16 });
17
18 setCall(newCall);
19};
20
21// In your JSX:
22<Pressable onPress={() => makeCall('+15551234567')}>
23 <Text>Call</Text>
24</Pressable>
25<Pressable onPress={() => call?.hangup()}>
26 <Text>Hang Up</Text>
27</Pressable>

Step 7: Handle Incoming Calls

Use React Native’s Alert for a simple incoming call prompt:

1import { Alert } from 'react-native';
2
3// After creating the client:
4c.on('incoming', (incomingCall) => {
5 Alert.alert(
6 'Incoming Call',
7 `From: ${incomingCall.remoteIdentity}`,
8 [
9 {
10 text: 'Decline',
11 style: 'destructive',
12 onPress: () => incomingCall.hangup(),
13 },
14 {
15 text: 'Answer',
16 onPress: () => {
17 incomingCall.answer();
18 setCall(incomingCall);
19 // Bind call events...
20 },
21 },
22 ],
23 );
24});

Step 8: Add Call Controls

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

Step 9: Call Different Targets

1// Call another registered user
2client.callUser('alice');
3
4// Take a call from a queue
5client.callQueue('support');
6
7// Join a conference room
8client.callConference('standup-meeting');
9
10// Call a jambonz application
11client.callApplication('your-app-sid');

Step 10: Using React Hooks (Alternative)

1import { useJambonzClient, useCall } from '@jambonz/client-sdk-react-native';
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 <View>
14 <Text>Status: {client.state}</Text>
15
16 {!client.isRegistered && (
17 <Pressable onPress={client.connect}>
18 <Text>Connect</Text>
19 </Pressable>
20 )}
21
22 {client.isRegistered && !call.isActive && (
23 <Pressable onPress={() => call.makeCall('+15551234567')}>
24 <Text>Call</Text>
25 </Pressable>
26 )}
27
28 {call.isActive && (
29 <>
30 <Text>Call: {call.state}</Text>
31 <Pressable onPress={call.toggleMute}>
32 <Text>{call.isMuted ? 'Unmute' : 'Mute'}</Text>
33 </Pressable>
34 <Pressable onPress={call.hangup}>
35 <Text>Hang Up</Text>
36 </Pressable>
37 </>
38 )}
39 </View>
40 );
41}

Android Notes

  • JDK 17+ required — install via brew install --cask zulu@17
  • USB debugging must be enabled for physical devices
  • Works on both emulator and physical device

iOS Notes

  • Xcode 15+ required
  • You must configure code signing in Xcode (Signing & Capabilities tab)
  • Add microphone permission in Info.plist
  • Works on both simulator and physical device (physical device recommended for real calls)

Run the Full Example App

The repo includes a complete softphone example with a dark theme UI, DTMF dial pad, incoming call handling, mute/hold/hangup controls.

$# 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 the React Native example
$cd examples/react-native
$npm install
$
$# 4. Generate the native projects
$npx @react-native-community/cli init JambonzExample --directory /tmp/JambonzExample --skip-install
$cp -r /tmp/JambonzExample/android ./android
$cp -r /tmp/JambonzExample/ios ./ios
$rm -rf /tmp/JambonzExample

After generating, add the required permissions:

Android — add to android/app/src/main/AndroidManifest.xml inside <manifest>:

1<uses-permission android:name="android.permission.INTERNET" />
2<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
3<uses-permission android:name="android.permission.RECORD_AUDIO" />
4<uses-permission android:name="android.permission.CAMERA" />
5<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

iOS — add to ios/JambonzExample/Info.plist before </dict>:

1<key>NSMicrophoneUsageDescription</key>
2<string>Required for voice calls</string>

Run on Android

$# Start Metro bundler
$npx react-native start
$
$# In another terminal — run on device or emulator
$npx react-native run-android

Run on iOS

$# Install CocoaPods
$cd ios && pod install && cd ..
$
$# Open Xcode to configure signing
$open ios/JambonzExample.xcworkspace
$# → Select your Team in Signing & Capabilities
$# → Change Bundle Identifier to something unique
$
$# Start Metro bundler
$npx react-native start
$
$# In another terminal — run on device or simulator
$npx react-native run-ios
$# Or for a specific device:
$npx react-native run-ios --device

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
  • src/theme.ts — shared color palette

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. Tap Connect — status dot turns green when registered
  4. Enter a number or SIP target and tap Call
  5. Use the in-call controls: mute, hold, DTMF pad, hang up
  6. Incoming calls show an answer/decline prompt

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