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>:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Step 4: iOS Permissions

Add to ios/MyJambonzApp/Info.plist:

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

Step 5: Connect to the SBC

// App.tsx
import React, { useState } from 'react';
import { View, Text, TextInput, Pressable } from 'react-native';
import {
createJambonzClient,
JambonzClient,
ClientState,
} from '@jambonz/client-sdk-react-native';
function App() {
const [client, setClient] = useState<JambonzClient | null>(null);
const [state, setState] = useState<ClientState>(ClientState.Disconnected);
const connect = async () => {
const c = createJambonzClient({
server: 'wss://sbc.example.com:8443',
username: 'your-username',
password: 'your-password',
});
c.on('stateChanged', (s) => setState(s));
c.on('error', (err) => console.log('Error:', err.message));
await c.connect();
setClient(c);
};
return (
<View style={{ padding: 40 }}>
<Text>Status: {state}</Text>
<Pressable onPress={connect}>
<Text>Connect</Text>
</Pressable>
</View>
);
}
export 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

const [call, setCall] = useState(null);
const [callState, setCallState] = useState(null);
const makeCall = (target: string) => {
if (!client) return;
const newCall = client.call(target);
newCall.on('stateChanged', (s) => setCallState(s));
newCall.on('ended', () => {
setCall(null);
setCallState(null);
});
newCall.on('failed', () => {
setCall(null);
setCallState(null);
});
setCall(newCall);
};
// In your JSX:
<Pressable onPress={() => makeCall('+15551234567')}>
<Text>Call</Text>
</Pressable>
<Pressable onPress={() => call?.hangup()}>
<Text>Hang Up</Text>
</Pressable>

Step 7: Handle Incoming Calls

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

import { Alert } from 'react-native';
// After creating the client:
c.on('incoming', (incomingCall) => {
Alert.alert(
'Incoming Call',
`From: ${incomingCall.remoteIdentity}`,
[
{
text: 'Decline',
style: 'destructive',
onPress: () => incomingCall.hangup(),
},
{
text: 'Answer',
onPress: () => {
incomingCall.answer();
setCall(incomingCall);
// Bind call events...
},
},
],
);
});

Step 8: Add Call Controls

const [isMuted, setIsMuted] = useState(false);
const [isHeld, setIsHeld] = useState(false);
// Bind events on the call:
newCall.on('mute', (muted) => setIsMuted(muted));
newCall.on('hold', (held) => setIsHeld(held));
// In your JSX:
<Pressable onPress={() => call?.toggleMute()}>
<Text>{isMuted ? 'Unmute' : 'Mute'}</Text>
</Pressable>
<Pressable onPress={() => isHeld ? call?.unhold() : call?.hold()}>
<Text>{isHeld ? 'Resume' : 'Hold'}</Text>
</Pressable>

Step 9: Call Different Targets

// Call another registered user
client.callUser('alice');
// Take a call from a queue
client.callQueue('support');
// Join a conference room
client.callConference('standup-meeting');
// Call a jambonz application
client.callApplication('your-app-sid');

Step 10: Using React Hooks (Alternative)

import { useJambonzClient, useCall } from '@jambonz/client-sdk-react-native';
function Phone() {
const client = useJambonzClient({
server: 'wss://sbc.example.com:8443',
username: 'your-username',
password: 'your-password',
});
const call = useCall(client.client);
return (
<View>
<Text>Status: {client.state}</Text>
{!client.isRegistered && (
<Pressable onPress={client.connect}>
<Text>Connect</Text>
</Pressable>
)}
{client.isRegistered && !call.isActive && (
<Pressable onPress={() => call.makeCall('+15551234567')}>
<Text>Call</Text>
</Pressable>
)}
{call.isActive && (
<>
<Text>Call: {call.state}</Text>
<Pressable onPress={call.toggleMute}>
<Text>{call.isMuted ? 'Unmute' : 'Mute'}</Text>
</Pressable>
<Pressable onPress={call.hangup}>
<Text>Hang Up</Text>
</Pressable>
</>
)}
</View>
);
}

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>:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

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

<key>NSMicrophoneUsageDescription</key>
<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