Integrating VoIP into Flutter Apps: SIP Stack, CallKit, and Push Notifications
How to build a production-grade VoIP feature in a Flutter app — SIP registration, background call handling, CallKit on iOS, ConnectionService on Android, and SRTP media.
Integrating VoIP into Flutter Apps: SIP Stack, CallKit, and Push Notifications
How to build a production-grade VoIP feature in a Flutter app — SIP registration, background call handling, CallKit on iOS, ConnectionService on Android, and SRTP media.
Kaushik Parmar
Founder & VoIP Architect, CelloIP Technologies
The Challenge of Mobile VoIP
Mobile VoIP is significantly harder than desktop or server VoIP for three reasons. First, mobile operating systems aggressively kill background processes to save battery — a SIP registration cannot be maintained by a background app. Second, inbound calls must wake a locked device and show a native calling UI — in iOS this requires CallKit integration via APNs VoIP push; in Android it requires ConnectionService via FCM. Third, NAT traversal is more complex on mobile networks where carrier-grade NAT (CGNAT) is ubiquitous. A production Flutter VoIP app must solve all three. The technology stack: dart-sip-ua for SIP signalling, flutter_callkit_incoming for the native call screen, firebase_messaging for Android push, and flutter_webrtc for media.
SIP Registration with dart-sip-ua
dart-sip-ua provides a Dart-native SIP stack that handles REGISTER, INVITE, re-INVITE, BYE, and DTMF. The transport layer supports WebSocket (wss://) which works reliably on mobile networks and through CGNAT.
sip_service.dart — SIP registration and call setup
import 'package:dart_sip_ua/dart_sip_ua.dart';
class SipService implements SipUaHelperListener {
final _helper = SipUaHelper();
void init(String ext, String password, String serverWss) {
final settings = UaSettings()
..webSocketUrl = serverWss // e.g. wss://sip.celloip.com:8089/ws
..uri = 'sip:[email protected]'
..authorizationUser = ext
..password = password
..displayName = ext
..dtmfMode = DtmfMode.RFC2833;
_helper.addSipUaHelperListener(this);
_helper.start(settings);
}
@override
void registrationStateChanged(RegistrationState state) {
debugPrint('SIP status: ${state.state}');
}
@override
void callStateChanged(Call call, CallState state) {
if (state.state == CallStateEnum.CALL_INITIATION) {
// Show incoming call UI via flutter_callkit_incoming
showIncomingCallScreen(call);
}
}
void makeCall(String destination) {
_helper.call('sip:[email protected]',
voiceonly: true);
}
}iOS: APNs VoIP Push + CallKit
When the Flutter app is backgrounded or the device is locked, the SIP WebSocket connection is terminated by iOS. Inbound calls arrive via APNs VoIP push (a special push channel with higher priority than standard push). Your Asterisk or FreeSWITCH server sends the push when a call arrives for the extension. The app wakes, re-registers SIP, and shows the CallKit native ringing screen — all within 500ms. Key setup: register for VoIP push via flutter_callkit_incoming, store the APNs device token on your server mapped to the SIP extension, and configure Asterisk's chan_pjsip to send APNs push via a push notification script.
main.dart — CallKit incoming call notification
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
Future<void> showIncomingCallScreen(String callerId, String uuid) async {
final params = CallKitParams(
id: uuid,
nameCaller: callerId,
appName: 'CelloIP',
type: 0, // 0 = voice
duration: 30000,
android: AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#1BA8DC',
),
ios: IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: false,
maximumCallGroups: 1,
maximumCallsPerCallGroup: 1,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(params);
}Android: FCM Push + ConnectionService
Android handles background VoIP calls via Firebase Cloud Messaging (FCM) with a high-priority data message. When the FCM message arrives, your app launches a headless Dart isolate via firebase_messaging's onBackgroundMessage handler, registers SIP, and shows the native call screen via flutter_callkit_incoming's ConnectionService integration. Android 14+ requires declaring the FOREGROUND_SERVICE_PHONE_CALL permission and a proper telecom ConnectionService manifest declaration.
SRTP: Encrypting Media on Mobile
Mobile VoIP calls traverse public Wi-Fi and mobile networks where packet capture is trivial. Always use SRTP (Secure RTP) for media encryption. dart-sip-ua enables SRTP via the flutter_webrtc's RTCPeerConnection with DTLS key negotiation. Your SIP server must support sRTP — Asterisk PJSIP enables this via `media_encryption = sdes` or `dtls` in the endpoint configuration. Without SRTP, call audio on public Wi-Fi is plaintext.
Frequently Asked Questions
Can dart-sip-ua handle video calls?
Yes, via flutter_webrtc for the media layer. dart-sip-ua negotiates the video SDP offer/answer and flutter_webrtc renders the video tracks.
What is the battery impact of a VoIP app?
With push-based wake (no persistent WebSocket), the battery impact is near zero in the background. The SIP WebSocket only connects when a call is active. A persistent WebSocket background registration drains 3–8% extra battery per day.
Does this work with Asterisk and FreeSWITCH both?
Yes. dart-sip-ua connects via WebSocket to any compliant SIP server. CelloIP has production Flutter apps registered to both Asterisk PJSIP WSS endpoints and FreeSWITCH mod_sofia WSS profiles.
Need help implementing this for your project?
Talk to a VoIP Engineer