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.

Tutorials19 min readDecember 21, 2025

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.

FlutterVoIPMobile

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.

Back to Blog

Need help implementing this for your project?

Talk to a VoIP Engineer