Voice
App-to-app and PSTN calling with a native call UI, IVR, transfers, conferencing and recording, on Inverge's own media plane.
Nexus Voice is a programmable call platform. A call is a session with one or more legs (participants). Media flows over Inverge's media plane (with TURN relay where needed); you never run an SFU, a TURN server or a codec. The Flutter SDK brings its own native call stack — CallKit + PushKit on iOS, a self-managed Telecom ConnectionService on Android — so incoming calls ring a locked or killed phone with the system call screen.
Concepts
| Term | Meaning |
|---|---|
| Identity | A callable address in an environment — a user id, an agent, a desk. |
| Device | A registered push target of an identity. All of them ring. |
| Call session | One logical call, from first attempt to teardown. |
| Leg | One participant's involvement — WebRTC (app), PSTN or SIP. |
| IVR flow | A node graph a caller walks through with DTMF or in-app input. |
Flutter — calling
await Nexus.init(const NexusConfig(apiKey: 'nxs_live_xxx', voiceEnabled: true));
final voice = Nexus.instance.voice;
await voice.ensurePermissions(); // ask for the mic during onboarding
await voice.placeCall(to: 'pilot_42'); // app ↔ app
await voice.placeCall(to: '+15551234567', type: VoiceEndpointType.pstn); // phone number
await voice.placeCall(to: 'pilot_42', record: true, metadata: {'order': '42'});
ValueListenableBuilder<NexusCall?>(
valueListenable: voice.current,
builder: (_, call, __) => call == null ? const SizedBox() : CallScreen(call),
);
await voice.setMuted(true); await voice.setSpeakerphone(true);
await voice.setHold(true); await voice.sendDtmf('1');
await voice.hangup();NexusCall.state moves through connecting → ringing → connected (and
onHold, reconnecting) to ended, failed, rejected, cancelled or
timeout. Every media, native or push failure surfaces as a call state — the SDK
never throws into your app.
Incoming calls
Nothing to code: the SDK registers each device's VoIP / FCM token for the
signed-in identity and the backend pushes the ring. Accept, Decline,
mute and hold from the system UI are handled, and a caller-side cancel becomes a
missed call. Nexus.init must run in main() — a VoIP push launches a killed iOS
app in the background.
- Capabilities: Push Notifications; Background Modes → Voice over IP and Audio, AirPlay, and Picture in Picture.
Info.plist:NSMicrophoneUsageDescription— required; without it iOS terminates the app when a call opens audio.- Upload the APNs
.p8key in the console (Push settings). The same key serves normal pushes and VoIP. - Optional:
NexusCallAppNameinInfo.plist,NexusCallIconin the asset catalog. CallKit is unavailable in mainland China — calls there use the in-app UI.
FCM must be configured (as for push). Add USE_FULL_SCREEN_INTENT for Android
14+. The SDK shows the full-screen incoming-call notification.
Server-side call control
session = client.voice.create_call(direction="outbound", recording_mode="mixed", metadata={"order": "42"})
sid = session["session"]["id"]
agent = client.voice.add_leg(sid, "agent", "webrtc", identity_id="agent_7")
agent["join"] # {room, region, access, turn} — hand to the device SDK
callee = client.voice.add_leg(sid, "callee", "pstn", address="+9647500000000", caller_id="+9647400000000")
if "error" in callee: # call_blocked · no_outbound_trunk · media_unavailable
...
client.voice.hangup(callee["leg"]["id"], reason="completed")
client.voice.transfer_blind(sid, leg_id, "pstn", target_address="+964…")Adding a leg reports failure inside a 200
A blocked call, a missing carrier or an unreachable media plane arrives as a successful response with an error code, so a client can end the call cleanly. Always check it before assuming the leg is live.
IVR, transfers & conferences
- IVR — build flows in the console's visual builder.
startIvr/pressIvrKeyon Flutter (rendered as an overlay),ivr_start/ivr_input/ivr_advanceon servers. An instruction can connect the caller to a number, an identity or a queue. - Transfers — blind, and warm (start → complete or cancel). Finish a warm transfer, or the other party stays on hold.
- Conferences — mute and remove participants.
Recordings & quality
Recording starts at answer (so unanswered calls never produce one). Recordings have no permanent URL: the console mints a short-lived token; treat the link as a secret. Clients report quality samples (RTT, MOS, candidate type) and the console charts them per call. Hang-up reasons distinguish missed from declined calls in reports.
Reference
Session replay
Watch what the person actually did — screens, taps, console and network — with text masked by default and correlated to the session.
Telescope
Every request, job, command and scheduled task with its whole journey — queries, N+1s, HTTP calls, cache, logs and exceptions — for every server framework.
