Building iOS VoIP Apps with Swift and CallKit: Complete Guide

How to build a production iOS VoIP app in Swift: CallKit integration, PushKit for background VoIP calls, PJSIP/linphone SDK setup, audio session management, and App Store submission for VoIP apps.

Mobile Development14 min readApril 14, 2026

Building iOS VoIP Apps with Swift and CallKit: Complete Guide

How to build a production iOS VoIP app in Swift: CallKit integration, PushKit for background VoIP calls, PJSIP/linphone SDK setup, audio session management, and App Store submission for VoIP apps.

Kaushik Parmar

Founder & VoIP Architect, CelloIP Technologies

Why Native Swift for iOS VoIP?

Native Swift development gives the deepest access to iOS APIs: CallKit for system-level call UI, PushKit for reliable background wakeup, AVAudioEngine for professional audio processing, and direct PJSIP/linphone SDK integration without abstraction layers. For production VoIP apps, native Swift delivers the best call quality, the most reliable background behaviour, and the cleanest App Store review path — especially for enterprise apps handling SRTP encrypted calls.

CallKit Integration in Swift

CallKit lets your VoIP app show native iOS call screens, appear in the Recents list, and integrate with Siri, CarPlay, and Bluetooth headsets. You implement CXProvider (to report calls to the system) and CXCallController (to request call actions). When an incoming call arrives via PushKit push, you call reportNewIncomingCall() on CXProvider before the push expires. When the user answers, CallKit calls provider(_:perform:) with CXAnswerCallAction — this is where you answer the SIP call in PJSIP.

AppDelegate.swift — CallKit provider setup

import CallKit
import PushKit

class CallProvider: NSObject, CXProviderDelegate {
  let provider: CXProvider
  let callController = CXCallController()

  override init() {
    let config = CXProviderConfiguration()
    config.supportsVideo = false
    config.maximumCallsPerCallGroup = 1
    config.supportedHandleTypes = [.phoneNumber, .generic]
    self.provider = CXProvider(configuration: config)
    super.init()
    provider.setDelegate(self, queue: nil)
  }

  func reportIncomingCall(uuid: UUID, handle: String, displayName: String) {
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .generic, value: handle)
    update.localizedCallerName = displayName
    provider.reportNewIncomingCall(with: uuid, update: update) { error in
      if let error = error { print("CallKit error: \(error)") }
    }
  }

  func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    // Answer the SIP call here via your PJSIP bridge
    SIPManager.shared.answerCall(callId: action.callUUID.uuidString)
    action.fulfill()
  }
}

PushKit for Background VoIP Calls

PushKit is mandatory for background VoIP call reception on iOS. Regular APNs push notifications cannot reliably wake a suspended VoIP app. PushKit wakes your app within milliseconds, giving it CPU time to register the incoming call with CallKit before the push expires. Critical: Apple requires that every PushKit push immediately triggers a CallKit reportNewIncomingCall() call. Apps that receive PushKit pushes without reporting a CallKit call are rejected from the App Store.

SwiftiOSVoIPCallKitPushKitPJSIP

Frequently Asked Questions

Which SIP library should I use for iOS Swift?

PJSIP 2.x (cross-platform, battle-tested, wide codec support) and linphone SDK (easier integration, commercial support available) are the two main options. PJSIP gives more control; linphone is faster to integrate.

Why does my VoIP app get rejected from the App Store?

Common rejection reasons: using PushKit without immediately reporting a CallKit call, abusing the VoIP background mode for non-call purposes, not declaring voip in UIBackgroundModes, or failing to handle call state transitions correctly in CXProvider.

How do I handle audio interruptions (Siri, other calls)?

Implement AVAudioSessionDelegate and listen for AVAudioSession.interruptionNotification. Pause your SIP call's audio on interruption, resume on interruptionEnded with shouldResume=true. Also handle route changes (speaker, Bluetooth, headphones) via routeChangeNotification.

Back to Blog

Need help implementing this for your project?

Talk to a VoIP Engineer