Skip to main content
Add Vobiz voice calls to a Flutter app on Android. The vobiz_webrtc SDK signs in to Vobiz over SIP on a WebSocket and carries the audio over WebRTC. Source code, API reference and example app: vobiz-ai/vobiz-flutter-sdk
Never put your Auth Token in the app. Your server keeps it and gives each signed-in user a short-lived access token.

Features

  • Make and receive calls, to phone numbers and SIP endpoints
  • Hold, mute, loudspeaker and DTMF
  • Call quality metrics: MOS, jitter, round-trip time and packet loss
  • Sign-in with short-lived access tokens, renewed without dropping a call
  • Caller ID, call recording and custom X-VH- SIP headers
  • Calls that survive short network drops
  • Incoming calls while the app is closed, through Firebase push
  • Conference calls, through your server

Usage

Credentials

To make and receive calls with the SDK, you need:
  1. A Vobiz Console account.
  2. Your Auth ID and Auth Token, from API Credentials on the dashboard. They stay on your server.
  3. An application in Voice Applications whose Answer URL is on your server. Copy its Application ID: calls that the app places go to this Answer URL.
  4. A Vobiz number from Numbers, to use as the caller ID.
Your server uses these to mint an access token for each signed-in user. See Sign in to the Vobiz client.

Install the SDK

pubspec.yaml
Then run flutter pub get. The SDK needs Flutter 3.19 or later and Android 7.0 (API 24) or later.

Android configuration

Add these permissions to android/app/src/main/AndroidManifest.xml:
AndroidManifest.xml
Android asks the user for the microphone when the first call starts. BLUETOOTH_CONNECT is a runtime permission on Android 12 and later: ask for it before you send audio to a headset.

Basic usage

Vobiz client

VobizClient is the core class of the SDK. It signs in, places and answers calls, and reports what happens on streams.
To change the defaults, pass an SdkConfig, for example VobizClient(config: const SdkConfig(autoReconnect: false)):

Logging configuration

Turn on debug to print SDK logs while you develop, or pass a Logger to send every line to your own logging:
Each line starts with [INFO], [WARN] or [ERROR]. The SDK never logs access tokens.

Sign in to the Vobiz client

Sign each user in with a short-lived access token that your server mints. The SDK asks your server for a new token before the current one expires, and registers again without dropping a call. 1. Mint the token on your server. Behind your own user authentication, call the Vobiz API with your Auth ID and Auth Token, read from the server’s environment:
server.js
The app then registers as <sub>_<auth_id>, for example alice_MA_XXXXXXXX.
Always set app: without it, the registrar can refuse the token with 10001 INVALID_ACCESS_TOKEN. Set nbf a minute in the past, because the registrar refuses a token that it thinks isn’t valid yet (10005 ACCESS_TOKEN_NOT_VALID_YET).
2. Sign in from the app. VobizBackendAccessTokenProvider posts {"reason": ...} to your token URL and reads {"token": ...} from the reply:
Pass the token URL at build time, so it isn’t written into your code:
Registration finishes after the sign-in call returns, so wait for RegistrationState.registered before you place a call. When the user signs out, call session.dispose() and then client.disconnect(). The client has four ways to sign in:

Make a call

makeCall places a call and returns its ID:
Vobiz then requests your application’s Answer URL, with To set to the number that the app dialled and the X-VH- headers included. Your Answer URL decides what the call does. For example, it can dial the number, using X-VH-Caller-ID as the caller ID:
Calls to phone numbers are billed to your account. makeCall throws an ArgumentError for an invalid header or caller ID before anything is dialled. See Custom SIP headers for the header rules.

Accept a call

Listen to callEvents to know when a call rings, connects or ends. Then accept the call from your answer button:
To ring the app, answer the inbound call on your number with a <Dial> to the user’s SIP identity:
To ring an endpoint, dial its SIP URI instead. See <User>.

Decline or end a call

DTMF (dual-tone multi-frequency)

The SDK sends the tones in the audio (RFC 4733) when the call supports it, and as SIP INFO otherwise.

Mute a call

client.isMuted gives the current state.

Toggle the loudspeaker

For Bluetooth and wired headsets, client.audioRoutes lists the outputs (availableRoutes()) and switches between them (selectRoute(route)).

Put a call on hold

If Vobiz refuses, the method throws a VobizException (hold_failed or resume_failed), and the call carries on as it was.

Call quality metrics

Once audio flows, the SDK measures the call every 2 seconds and reports a CallQualityEvent on callMediaEvents. You don’t need to turn anything on:
To read the latest figures at any time, call client.getCallStats(). It returns rttMs, jitterMs and packetLossPercent.

CallQualitySample properties

A value is null until WebRTC has measured it. mos is an estimate from a simplified ITU-T G.107 E-model, not a measurement.

CallQualityLevel enum

levelChanged is true only when the level moves, and the level moves only after two samples in a row agree, so a warning doesn’t flicker.

Advanced usage: push notifications

How push works on Vobiz

The SDK signs in over a WebSocket. When Android closes your app, the socket closes, and Vobiz can’t ring the app. To ring a closed app, your server answers the call and wakes the app with a Firebase Cloud Messaging (FCM) push:
  1. A call reaches your Vobiz number, and Vobiz requests your server’s Answer URL.
  2. Your server sends the user’s devices a high-priority FCM data message.
  3. The app shows the system call screen and signs in. Your server waits for that, then dials the user with a PushRef SIP header set to the push’s call_id:
  4. If the caller hangs up first, your server sends a call_ended message.
The example push server in the SDK repository (example/push_bridge) does all four steps. Its push data has these keys, and every value is a string:

Add push notifications to your Android app

The SDK reads the push (VobizIncomingPush) and finds the call that it announced. The call screen in these steps comes from the flutter_callkit_incoming plugin, as in the SDK’s example app.
  1. Add Firebase to your app. Create a Firebase project, run flutterfire configure to add its configuration, and then add the packages:
  2. Send the device’s FCM token to your server, with the signed-in user, so your server knows where to push:
  3. Show the call screen when a push arrives. Register one top-level handler for pushes in the background and in the foreground:
    main.dart
    The ck prefix keeps the plugin’s CallEvent apart from the SDK’s.
  4. Answer the call that the user accepted. Accept on the call screen opens your app. Sign in, then answer the INVITE whose PushRef matches the push, and only that one:
  5. Report a decline. The app hasn’t signed in, so it can’t reject the call itself. When the user taps Decline, send the push’s call_id to your server and have it hang up the caller. The example push server’s POST /push/decline does this. Also stop ringing on your server after ttl_ms, in case the decline never arrives.

Best practices for push notifications on Android

  1. Ask for permissions when the user signs in, not on the first ring. Android 13 and later need the notification permission (FirebaseMessaging.instance.requestPermission()). On Android 14 and later, the call screen needs Full screen notifications to take over a locked screen: check it with ck.FlutterCallkitIncoming.canUseFullScreenIntent() and ask for it with requestFullIntentPermission(). Without it, the call shows as a heads-up notification.
  2. Test a release build, with the app swiped away. Android delivers no pushes to a force-stopped app, and stopping a debug build from your IDE force-stops it.
  3. Handle late pushes. A push can arrive after the caller has given up. push.isExpired() is true once ttl_ms has passed: show a missed call instead of ringing.
  4. Handle an early answer. The user can tap Accept before the app has signed in. Keep the push, as step 4 does, and answer when the matching INVITE arrives. If it hasn’t arrived within about 60 seconds, end the call screen with ck.FlutterCallkitIncoming.endCall(id).
  5. Show call waiting when the user is on a call. When push.lineBusy is true, show a notification instead of the full-screen call screen.
  6. Remove the camera permissions that flutter_callkit_incoming adds if your app is audio-only. Otherwise, Play Console asks you to justify a camera foreground service:
    AndroidManifest.xml

Conference calls

VobizConference turns the current call into a Vobiz conference room, so you can add people and manage them. Moving calls and dialling people need your Auth Token, so the SDK asks your server, which makes the Vobiz API calls:
The first add moves both sides of the call into a new room, and then dials the person in. People you add see callerId, which must be a number on your account. Your server implements four routes, and each one answers with the room’s state: The example push server in the SDK repository implements all four.

Additional resources

Questions? Email support@vobiz.ai.

License

MIT. See LICENSE.