React Native Voicebot UI Kit
enx-voice-bot-react-native lets a React Native app place an outbound call to an
EnableX Dialogs voice bot and hold a live, two-way voice conversation with it. Unlike the
Chatbot UI Kit, there is no chat-bubble style prebuilt screen to attach — a voice call has
no message list to render. Instead, the package gives you a single call-control client
(EnxVoiceClient) that handles the REST session, the real-time Pipecat WebSocket
audio stream, microphone capture, and speaker playback for you, and reports call/mute/speaking
state back through a listener so your app can drive a minimal call screen (connect button, mute
toggle, speaking indicators) with whatever look you want.
First release of the React Native Voicebot UI Kit, published on npm. See Release Notes for what's included.
View on NPMThis is the first Voicebot UI Kit platform published on this site — Android, iOS, and Flutter follow the same call-control-client model. Use this page's structure as the reference when those platforms ship.
Requirements
| Requirement | Version |
|---|---|
| React | 18.0.0 or later |
| React Native | 0.71.0 or later (validated with the bundled ExampleApp on 0.73.0) |
| iOS deployment target | 15.0 or later |
| Android | Standard React Native 0.71+ Android toolchain; Gradle autolinking |
| Node.js | 18 or later |
| Xcode | Recent stable version |
| CocoaPods | For installing native iOS dependencies |
| Virtual number & voice bot config | Provided by your EnableX account / dialog setup |
Installation
Add the package
npm install enx-voice-bot-react-native
Or, if the SDK is provided as a local tarball:
npm install file:./enx-voice-bot-react-native-1.0.0.tgz
Peer dependencies (must already exist in your app): react (≥18.0.0) and react-native (≥0.71.0). Installing the package automatically pulls in react-native-permissions, react-native-live-audio-stream, protobufjs, and eventemitter3 as runtime dependencies — no separate install step is needed for these.
iOS setup
- Install native pods:
cd ios && pod install - Confirm your Podfile platform target is iOS 15.0 or later — the SDK's podspec requires it.
- Add the microphone usage description to Info.plist (see Required Permissions).
Android setup
- No manual linking is required — React Native autolinking registers the SDK's native module.
- Add the required permissions to
AndroidManifest.xml(see Required Permissions). - Rebuild the app:
npx react-native run-android
Required Permissions MANDATORY
The SDK requests microphone access at runtime before every call, but the underlying platform permission must be declared by the host app.
Android — AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
| Permission | Purpose |
|---|---|
INTERNET | Required for the REST call and WebSocket media connection. |
RECORD_AUDIO | Required to capture microphone audio. |
MODIFY_AUDIO_SETTINGS | Required to route call audio through the speakerphone / communication device. |
iOS — Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to talk to the voice bot.</string>
iOS shows this text to the user the first time the app requests microphone access; the SDK will not proceed with a call if this key is missing.
Runtime permission flow
When EnxVoiceClient.connect() is called, the SDK automatically checks the microphone permission and requests it if it has not been granted yet. If the user denies the permission, connect() fails and the listener's onError() callback receives an error message — no call is placed.
Configuration
Use EnxSdkConfig to choose which EnableX environment the SDK talks to.
| Configuration | Base URL | Logging |
|---|---|---|
EnxSdkConfig.qa | https://voiceagent-qa.enablex.io | Enabled (verbose) |
EnxSdkConfig.production | https://voiceagent.enablex.io | Disabled |
For a custom endpoint (for example, a private or regional deployment), build a config from an environment object:
import { EnxSdkConfig } from 'enx-voice-bot-react-native';
const config = EnxSdkConfig.fromEnvironment(
{ name: 'Staging', callApiBaseUrl: 'https://voiceagent-staging.example.com' },
{ enableLogging: true },
);
You can also override the call API base URL for a single call without changing the SDK config, by passing callApiBaseUrl to connect() — see Connect and disconnect.
Integration Steps
- Install the package and, on iOS, run
pod install(see Installation above). - Add the Android manifest permissions and the iOS Info.plist microphone usage description (see Required Permissions above).
- Implement
EnxVoiceListenerto receive call, mute, error, and speaking-state callbacks — see the Usage Example. - Create an
EnxVoiceClient, call.init(virtualNumber),.setEnxVoiceListener(...), and.setSdkConfig(EnxSdkConfig.qa or .production). - Call
connect()to start a call anddisconnect()to end it; usemuteAudio()andsendDtmf()as needed during the call. - Switch to
EnxSdkConfig.production(orfromEnvironmentwithenableLogging: false) before release builds to disable verbose SDK logging. - Test on real Android and iOS devices — see Developer Troubleshooting if a call fails to connect.
Module Exports
Import everything from the package root:
import {
EnxVoiceClient,
EnxVoiceListener,
EnxVoiceState,
EnxSdkConfig,
EnxVoiceEnvironment,
} from 'enx-voice-bot-react-native';
| Export | Kind |
|---|---|
EnxVoiceClient | Class — main entry point for placing and controlling a voice bot call. |
EnxVoiceListener | Interface (TypeScript type) — callbacks for call/audio events. |
EnxVoiceState | Enum — call connection states. |
EnxSdkConfig | Class — SDK environment/logging configuration. |
EnxVoiceEnvironment | Type + constants — REST base URL definitions (QA, Production). |
encodePipecatFrame / decodePipecatFrame | Functions — low-level protobuf frame helpers (advanced use only). |
EnxVoiceClient class
Controls the lifecycle of a single outbound voice bot call: requesting permissions, calling the REST API, opening the Pipecat WebSocket, and streaming audio.
Constructor
new EnxVoiceClient()
Creates a new client with its own audio player and recorder. Each instance manages at most one call at a time.
Configuration methods
setSdkConfig(config: EnxSdkConfig): EnxVoiceClient
Sets the environment (QA/Production/custom) and logging behavior used for subsequent connect() calls. Returns this for chaining.
init(virtualNumber: string): EnxVoiceClient
Sets the virtual number (E.164 format) that will be dialed when connect() is called. Returns this for chaining.
setEnxVoiceListener(listener: EnxVoiceListener): EnxVoiceClient
Registers the listener that receives call, mute, error, and speaking-state callbacks. Returns this for chaining.
setOnDtmfKeypadChanged(callback?: (showKeypad: boolean) => void): EnxVoiceClient
Registers a callback invoked when the active call session reports whether DTMF keypad input is available. Returns this for chaining.
Call control methods
connect(args?: { callApiBaseUrl?: string }): Promise<void>
Starts a call: verifies/requests the microphone permission, initializes native audio, calls POST /v2token, and opens the WebSocket media session. If callApiBaseUrl is omitted, the URL from the configured EnxSdkConfig environment is used. Emits onStatus(connecting) then either onStatus(connected) or onStatus(failed) via the listener.
disconnect(): Promise<void>
Ends the active call: deletes the REST call session, closes the WebSocket, stops audio capture/playback, and resets internal state. Emits onStatus(disconnected) and onCallDisconnect().
muteAudio(isMute: boolean): Promise<boolean>
Mutes or unmutes the microphone. Returns true on success, false if an error occurred. Emits onMuteStateChanged() on success.
isMuted(): boolean
Returns the current microphone mute state.
sendDtmf(digits: string): Promise<boolean>
Sends DTMF digits (0–9, *, #) to the active call session via POST /v2token/{voiceId}/dtmf. Returns false if there is no active session, the input is empty, or it contains invalid characters.
Read-only properties
| Property | Type | Description |
|---|---|---|
activeVoiceId | string | undefined | The voice session ID returned by POST /v2token for the current call, if any. |
hasCallApiBaseUrl | boolean | Whether a non-empty call API base URL is currently set. |
isConnectingOrConnected | boolean | True while a call is being established or is active. |
hasNotifiedDisconnect | boolean | True once onCallDisconnect() has been delivered for the current/last call. |
EnxVoiceListener interface
Implement this interface and pass it to setEnxVoiceListener() to receive call events.
| Method | Called when… |
|---|---|
onCallConnected() | The WebSocket media session has successfully opened. |
onCallDisconnect() | The call has fully ended (user-initiated or by the remote side/error). Fires at most once per call. |
onError(message: string) | A recoverable or fatal error occurs (permission denial, REST failure, WebSocket error, etc.). |
onMuteStateChanged(isMuted: boolean) | The microphone mute state changes as a result of muteAudio(). |
onStatus(state: EnxVoiceState) | The overall call state transitions (see EnxVoiceState below). |
onUserSpeaking(isUserSpeaking: boolean) | The bot's voice-activity detector reports the user started/stopped speaking. |
onBotSpeaking(isBotSpeaking: boolean) | The bot starts or stops producing spoken audio. |
EnxVoiceState enum
| Value | Description |
|---|---|
connecting | Permissions are being verified and the call is being initiated. |
connected | The media WebSocket is open and audio can flow. |
disconnected | No active call. This is also the initial state before connect() is called. |
failed | The call failed to establish or was terminated due to an error. |
EnxSdkConfig class
Holds the REST environment and logging preference used by EnxVoiceClient.
Static members
| Member | Environment | Logging |
|---|---|---|
EnxSdkConfig.qa | https://voiceagent-qa.enablex.io | Enabled |
EnxSdkConfig.production | https://voiceagent.enablex.io | Disabled |
Static method
EnxSdkConfig.fromEnvironment(
environment: EnxVoiceEnvironment,
opts?: { enableLogging?: boolean }
): EnxSdkConfig
Builds a config from a custom or predefined environment. If opts.enableLogging is omitted, logging defaults to disabled for the production environment and enabled otherwise.
Instance members
| Member | Type | Description |
|---|---|---|
environment | EnxVoiceEnvironment | The REST environment this config points to. |
enableLogging | boolean | Whether the SDK emits verbose debug logs. |
toString() | string | Human-readable summary, e.g. "EnxSdkConfig(environment: QA, logging: true)". |
EnxVoiceEnvironment type
| Field | Description |
|---|---|
name | Human-readable environment name (e.g. "QA", "Production"). |
callApiBaseUrl | Base URL (no trailing slash) for the /v2token REST endpoints. |
Predefined constants: EnxVoiceEnvironment.qa and EnxVoiceEnvironment.production. Supply your own object with the same shape to target a custom deployment.
REST API Reference
EnxVoiceClient calls the following EnableX Voice Agent REST endpoints internally. They are documented here for troubleshooting and for advanced integrations that need to call them directly.
Start a call
POST {callApiBaseUrl}/v2token
Content-Type: application/json
Request body:
{
"phone": "<virtual number, E.164>"
}
On success, returns HTTP 200 with a JSON body containing voice_id (or
voiceId), a WebSocket URL (wss_host / wss_url /
wssUrl / pipecat_url / url), and an optional
dtmf flag.
Some deployments return this payload base64-encoded inside a top-level "data" field; the SDK decodes it automatically.
Used by EnxVoiceClient.connect().
End a call
DELETE {callApiBaseUrl}/v2token/{voiceId}
Returns HTTP 200 on success. Used by EnxVoiceClient.disconnect() (only if the call was successfully started).
Send DTMF digits
POST {callApiBaseUrl}/v2token/{voiceId}/dtmf
Content-Type: application/json
Request body:
{
"digits": "<0-9, *, #>"
}
Returns HTTP 200 on success. Used by EnxVoiceClient.sendDtmf(digits).
Media WebSocket
After POST /v2token succeeds, the SDK opens a WebSocket connection to the returned
URL and exchanges binary, protobuf-framed Pipecat messages: outgoing 16 kHz mono PCM
microphone audio frames, and incoming bot audio frames plus JSON control events such as
bot-started-speaking, bot-stopped-speaking,
user-started-speaking, and user-stopped-speaking.
Low-Level Helpers advanced
These are exported for custom integrations and testing; most applications should use EnxVoiceClient instead of calling them directly.
| Function | Description |
|---|---|
encodePipecatFrame(frame): Uint8Array | Serializes a Pipecat frame object (audio/text/message) to the wire protobuf format. |
decodePipecatFrame(bytes): PipecatFrame | Parses a raw WebSocket binary message into a typed Pipecat frame (audio, text, or message). |
Usage Example
Implement the listener
Your app implements EnxVoiceListener to receive call state, speaking state, mute state, and error events.
import {
EnxVoiceClient,
type EnxVoiceListener,
EnxVoiceState,
EnxSdkConfig,
} from 'enx-voice-bot-react-native';
class MyVoiceListener implements EnxVoiceListener {
onCallConnected() { /* update UI: call is live */ }
onCallDisconnect() { /* update UI: call ended */ }
onError(message: string) { /* show an error toast */ }
onMuteStateChanged(isMuted: boolean) { /* toggle mute icon */ }
onStatus(state: EnxVoiceState) { /* connecting / connected / disconnected / failed */ }
onUserSpeaking(isUserSpeaking: boolean) { /* animate mic indicator */ }
onBotSpeaking(isBotSpeaking: boolean) { /* animate bot indicator */ }
}
Create and configure the client
const client = new EnxVoiceClient()
.init('911100123457') // virtual number to dial
.setEnxVoiceListener(new MyVoiceListener())
.setSdkConfig(EnxSdkConfig.qa); // or EnxSdkConfig.production
Connect and disconnect
// Start a call
await client.connect();
// Optionally override the REST base URL for this call only
await client.connect({ callApiBaseUrl: 'https://voiceagent.enablex.io' });
// End the call
await client.disconnect();
Mute and unmute the microphone
await client.muteAudio(true); // mute
await client.muteAudio(false); // unmute
client.isMuted(); // current mute state
Send DTMF digits
If the bot session supports a DTMF keypad, the listener's onDtmfKeypadChanged callback (set via setOnDtmfKeypadChanged) is notified so your app can show a keypad. Digits are sent with:
const sent = await client.sendDtmf('123#');
sendDtmf() accepts digits 0–9, * and #, and returns false if there is no active call session or the input is invalid.
React to speaking state
While a call is active, onUserSpeaking(true/false) and
onBotSpeaking(true/false) fire as the bot's voice-activity detector toggles. The
SDK automatically pauses microphone upload while the bot is speaking and resumes it shortly
after the bot stops, to avoid echo/overlap.
Feature Support (v1.0.0)
| Feature | Supported | Notes |
|---|---|---|
| Outbound voice bot calling | Yes | Single REST call (POST /v2token) returns a voice session ID and a WebSocket URL. |
| Real-time full-duplex audio | Yes | WebSocket streaming using Pipecat protobuf frames (16 kHz mono microphone upload, bot audio playback). |
| Native audio bridge | Yes | Android and iOS native modules handle low-latency microphone capture and speaker playback. |
| Automatic permission handling | Yes | Microphone permission requested/verified automatically via react-native-permissions before a call starts. |
| Mute / unmute | Yes | muteAudio() / isMuted() during an active call. |
| DTMF digit sending | Yes | For calls where the bot indicates a keypad is available. |
| Speaking-state callbacks | Yes | onUserSpeaking / onBotSpeaking so the host app can show speaking indicators. |
| Selectable environments | Yes | Built-in QA and Production, or a fully custom environment via EnxSdkConfig.fromEnvironment(). |
| Prebuilt call UI (screens/components) | No | The package is a headless call-control client — your app builds its own call screen from the listener callbacks. |
| Inbound call handling | No | The SDK only initiates outbound calls to a virtual number. |
| Multiple concurrent calls | No | One active call per EnxVoiceClient instance; create a new instance (or fully disconnect) before starting another call. |
Known Issues & Limitations
- Outbound calls only — the SDK initiates calls to a virtual number; there is no inbound call handling.
- One active call per
EnxVoiceClientinstance — create a new instance (or fully disconnect) before starting another call. - No automated test suite is published with this release (
npm testis a placeholder). - DTMF sending requires the bot session to report keypad support; otherwise
sendDtmf()returnsfalse. - Verbose debug logging is enabled by default under the QA configuration; use
EnxSdkConfig.productionor setenableLogging: falsefor release builds.
Developer Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
connect() fails immediately with a permission error | Microphone permission was denied, or the platform usage-description (Info.plist / manifest) is missing. Prompt the user to enable it in system settings. |
| “callApiBaseUrl is not set” error | No environment was configured and no callApiBaseUrl was passed to connect(). Call setSdkConfig() or pass callApiBaseUrl explicitly. |
| No audio heard on Android | Confirm MODIFY_AUDIO_SETTINGS is declared and the device is not in silent/Do-Not-Disturb mode with media muted. |
sendDtmf() always returns false | There is no active call, or the bot session did not enable DTMF for this call. |
| Call connects but disconnects immediately | Check onError() output and server-side logs; the WebSocket endpoint returned an error or closed the session. |
Upgrade Notes
- This is the first published version; there are no breaking changes to migrate from.
- When upgrading in the future, re-run
pod installin the iOS project after bumping the package version, since native iOS sources ship inside the pod. - Review
EnxSdkConfigusage before release builds — useEnxSdkConfig.production(orfromEnvironmentwithenableLogging: false) to disable verbose SDK logging.