SDKs
One protocol. Five SDKs, Flutter next.
Every SDK wraps the same wire protocol, so an agent behaves the same whether the caller is in Chrome, on an iPhone or in an Android app.
Web
TypeScriptBrowser SDK over Socket.IO, WebRTC and mediasoup-client. Exposes the VoxeraClient class with a typed event map for session state, transcripts and audio.
npm install @voxera/sdk-webReact
TypeScriptThe browser SDK behind a hook. useVoxera owns the client lifecycle and its subscriptions, merges streamed chunks into one message per reply, and carries "use client" for the Next.js App Router.
npm install @voxera/sdk-reactReact Native
TypeScriptiOS and Android from one codebase. The useVoxera hook owns the client lifecycle, merges streamed chunks into one message per reply, and handles the Android microphone permission for you.
npm install @voxera/sdk-react-nativeiOS
SwiftNative Swift SDK exposing VoxeraClient and a SwiftUI-ready VoxeraViewModel, with AVAudioSession handling built in.
.package(url: "https://github.com/voxera-voice/voxera-ios.git", from: "1.1.43")Android
KotlinNative Kotlin SDK with coroutine-based session control and Flow events, served from JitPack.
implementation("com.github.voxera-voice:voxera-android:1.1.43") // repo: maven("https://jitpack.io")Flutter
DartDart SDK over a Pigeon-generated platform channel, sharing the same native iOS and Android transports.
Public release in progress — the source is available on request in the meantime.
The same call, on every platform
WebTypeScript
@voxera/sdk-webBrowser SDK over Socket.IO, WebRTC and mediasoup-client. Exposes the VoxeraClient class with a typed event map for session state, transcripts and audio.
import { VoxeraClient } from "@voxera/sdk-web";
const client = new VoxeraClient({
appKey: "vx_pk_live_...", // Publishable key; restrict its allowed origins.
serverUrl: "https://rtc.voxera-voice.com",
agentId: "your-agent-id", // Publish the agent in the dashboard first.
// Let the SDK attach and play the agent's audio itself.
autoPlayRemoteAudio: true,
});
client.on("message", (message) => {
console.log(message.role, message.fullText ?? message.content);
});
await client.connect();
client.startConversation();ReactTypeScript
@voxera/sdk-reactThe browser SDK behind a hook. useVoxera owns the client lifecycle and its subscriptions, merges streamed chunks into one message per reply, and carries "use client" for the Next.js App Router.
"use client";
import { useVoxera } from "@voxera/sdk-react";
export function VoiceButton() {
const call = useVoxera();
return (
<section aria-label="Voice assistant">
<button
type="button"
disabled={call.isBusy}
onClick={() => {
if (call.isActive) {
void call.leave();
} else {
void call.start({
appKey: "vx_pk_live_...", // Publishable key, never vx_sk_*.
serverUrl: "https://rtc.voxera-voice.com",
agentId: "your-agent-id",
});
}
}}
>
{call.isBusy ? "Connecting…" : call.isActive ? "End call" : "Talk to the agent"}
</button>
<p role="status">{call.connectionStatus}</p>
{call.error ? <p role="alert">{call.error}</p> : null}
{call.autoplayBlocked ? (
<button type="button" onClick={() => void call.resumeAudio()}>
Tap to hear the agent
</button>
) : null}
{call.messages.map((message) => (
<p key={message.id}><strong>{message.role}:</strong> {message.content}</p>
))}
</section>
);
}React NativeTypeScript
@voxera/sdk-react-nativeiOS and Android from one codebase. The useVoxera hook owns the client lifecycle, merges streamed chunks into one message per reply, and handles the Android microphone permission for you.
import { Button, Text, View } from "react-native";
import { useVoxera } from "@voxera/sdk-react-native";
export function VoiceScreen() {
const { start, leave, isActive, messages, speakingState } = useVoxera();
return (
<View>
<Text>{speakingState === "ai" ? "Agent speaking" : "Listening"}</Text>
<Button
title={isActive ? "End call" : "Start call"}
onPress={() =>
isActive
? leave()
: start({
appKey: "vx_pk_live_...",
serverUrl: "https://rtc.voxera-voice.com",
agentId: "your-agent-id",
userId: "user-123",
})
}
/>
{messages.map((message) => (
<Text key={message.id}>{message.content}</Text>
))}
</View>
);
}iOSSwift
VoxeraSDKNative Swift SDK exposing VoxeraClient and a SwiftUI-ready VoxeraViewModel, with AVAudioSession handling built in.
import SwiftUI
import VoxeraSDK
struct VoiceView: View {
// The view model takes its configuration up front — there is no
// separate configure() step.
@StateObject private var voxera = VoxeraViewModel(
config: VoxeraConfig(
appKey: publishableKey,
serverUrl: "https://rtc.voxera-voice.com",
userId: "user-123"
)
)
var body: some View {
VStack(spacing: 12) {
Text(voxera.isConnected ? "Connected" : "Idle")
ForEach(voxera.conversationMessages) { message in
Text(message.content)
}
Button("Start call") {
voxera.connect()
voxera.startConversation()
}
}
}
}AndroidKotlin
com.github.voxera-voice:voxera-androidNative Kotlin SDK with coroutine-based session control and Flow events, served from JitPack.
import com.voxera.sdk.ConversationMessage
import com.voxera.sdk.VoxeraClient
import com.voxera.sdk.VoxeraConfig
import com.voxera.sdk.VoxeraListener
// Configuration and the listener are passed to the constructor;
// connect() itself takes no arguments.
private val voxera = VoxeraClient(
context,
VoxeraConfig(
appKey = PUBLISHABLE_KEY,
serverUrl = "https://rtc.voxera-voice.com",
userId = "user-123",
listener = object : VoxeraListener {
override fun onMessage(message: ConversationMessage) {
binding.transcript.append(message.content)
}
},
),
)
voxera.connect()
voxera.startConversation()FlutterDart
Public release in progressDart SDK over a Pigeon-generated platform channel, sharing the same native iOS and Android transports.
import 'package:voxera_flutter/voxera_flutter.dart';
final voxera = VoxeraClient(const VoxeraConfig(
appKey: publishableKey,
serverUrl: 'https://rtc.voxera-voice.com',
userId: 'user-123',
));
// Messages arrive as typed events, not as a list.
final sub = voxera.on('message').listen((event) {
setState(() => _transcript.add(event.data['content'] as String));
});
await voxera.connect();
await voxera.startConversation();Hear it before you build on it
The demo is the real pipeline, not a recording. Talk to it, interrupt it, and decide from there.