Adding real-time voice chat to a Flutter app used to mean months of work — setting up media servers, handling WebRTC negotiation, managing connection state, and debugging platform-specific audio issues on both iOS and Android. With VoxaStream, you can go from zero to a working voice room in under an hour.
This guide walks you through the complete integration: creating an account, generating API keys, authenticating users from your backend, and connecting your Flutter app to a live voice room.
Prerequisites
- A Flutter project (Flutter 3.x or later)
- A backend server (Node.js, Python, Go, or any language that can make HTTP requests)
- A VoxaStream account — sign up free
Step 1: Get your API keys
After signing up, go to your dashboard and navigate to API Keys. Create a new key. You will receive two values:
vc_xxx— your secret API key. Keep this on your server only. Never embed it in your Flutter app.vc_client_xxx— your public client ID. Safe to embed in mobile apps for client-side auth flows.
Step 2: Authenticate users from your backend
The authentication flow is simple: your backend calls VoxaStream with the secret API key and gets back a short-lived session token. You then pass that token to your Flutter app. This keeps your secret key off the device entirely.
Here is an example in Node.js:
// Your backend — e.g. Express.js
app.post('/voice-token', async (req, res) => {
const { userId, username } = req.user; // your own auth
const response = await fetch('https://voxastream.com/api/auth/auto', {
method: 'POST',
headers: {
'Authorization': 'Bearer vc_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
custom_id: userId, // your own user ID
metadata: { plan: 'pro' }, // any extra data
}),
});
const { token } = await response.json();
res.json({ token });
});The token expires in 1 hour. Use the refresh_token from the same response to silently renew it without asking the user to re-authenticate.
Step 3: Fetch the token from Flutter
From your Flutter app, call your own backend endpoint to get the VoxaStream token. Never call VoxaStream directly with the secret key from the app.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> getVoiceToken() async {
final res = await http.post(
Uri.parse('https://your-api.com/voice-token'),
headers: {'Authorization': 'Bearer $yourAppToken'},
);
final data = jsonDecode(res.body);
return data['token'] as String;
}Step 4: Connect to a voice room
VoxaStream uses a WebSocket connection for signaling. Once connected, you join a channel and the SDK handles the audio negotiation automatically.
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
class VoiceRoomClient {
late WebSocketChannel _channel;
Future<void> connect(String token, int channelId) async {
_channel = WebSocketChannel.connect(
Uri.parse('wss://voxastream.com/ws?token=$token'),
);
// Join the channel as a speaker
_channel.sink.add(jsonEncode({
'type': 'join_channel',
'payload': {
'channel_id': channelId,
'role': 'speaker', // or 'listener'
},
}));
// Listen for messages
_channel.stream.listen((message) {
final msg = jsonDecode(message);
_handleMessage(msg['type'], msg['payload']);
});
}
void _handleMessage(String type, dynamic payload) {
switch (type) {
case 'channel_users':
// Update your UI with the list of users in the room
print('Users in room: ${payload['users']}');
break;
case 'webrtc_offer':
// Handle incoming audio offer (managed by the SDK)
break;
}
}
void mute(bool muted) {
_channel.sink.add(jsonEncode({
'type': 'mute',
'payload': {'muted': muted},
}));
}
void disconnect() {
_channel.sink.close();
}
}Step 5: Handle microphone permissions
Add the permission_handler package and request microphone access before connecting:
// pubspec.yaml
dependencies:
permission_handler: ^11.0.0
// In your widget
import 'package:permission_handler/permission_handler.dart';
Future<bool> requestMicPermission() async {
final status = await Permission.microphone.request();
return status.isGranted;
}
// Before connecting
if (await requestMicPermission()) {
await voiceClient.connect(token, channelId);
}On iOS, also add NSMicrophoneUsageDescription to your Info.plist. On Android, add <uses-permission android:name="android.permission.RECORD_AUDIO" /> to your AndroidManifest.xml.
Step 6: List available rooms
Before joining, you may want to show users a list of available voice channels:
Future<List<Map>> getRooms(String token) async {
final res = await http.get(
Uri.parse('https://voxastream.com/api/channels'),
headers: {'Authorization': 'Bearer $token'},
);
return List<Map>.from(jsonDecode(res.body));
}
// Returns:
// [
// { "id": 1, "name": "General" },
// { "id": 2, "name": "Gaming" },
// ]You're live
That's all it takes. Your Flutter app can now authenticate users, list rooms, and connect them to live voice channels — speaker or listener. The infrastructure handles codec negotiation, connection recovery, and audio routing so you don't have to.
Next steps: explore token refresh, room roles, and the full API reference.