InvergeDocs

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

TermMeaning
IdentityA callable address in an environment — a user id, an agent, a desk.
DeviceA registered push target of an identity. All of them ring.
Call sessionOne logical call, from first attempt to teardown.
LegOne participant's involvement — WebRTC (app), PSTN or SIP.
IVR flowA 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.

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 / pressIvrKey on Flutter (rendered as an overlay), ivr_start / ivr_input / ivr_advance on 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

Flutter · Python · .NET · REST

On this page