• Choose:

Make a call to a mobile app

During this guide you will learn how to setup and use the Telnyx RTC Native SDKs into your app to receive calls through push notifications.

Requirements

Build the iOS app

Configure the project

  1. Setup the TelnyxRTC iOS SDK into your project.

  2. Enable the following capabillities into your app:

    • Background mode: Audio, Airplay and Picture in picture
    • Background mode: Voice over IP
    • Push Notifications

Setup the Telnyx client

  1. Import the SDK into your ViewController
import TelnyxRTC
  1. Create and instance of the SDK
let client = TxClient()
  1. Setup the delegate
client.delegate = self
// MARK: - Callbacks from the Telnyx RTC SDK
extension ViewController: TxClientDelegate {
    func onSocketConnected() {}

    func onSocketDisconnected() {}

    func onClientError(error: Error) {}

    func onClientReady() {}

    func onSessionUpdated(sessionId: String) {}

    func onCallStateUpdated(callState: CallState, callId: UUID) {
        DispatchQueue.main.async {
            self.callStateLabel.text = "\(callState)"
        }
    }

    // This method will be fired when receiving a call while the client is connected
    func onIncomingCall(call: Call) {
        guard let incomingCallUUID = call.callInfo?.callId else {
            print("Unknwon incoming call..")
            return
        }

        if let currentCallUUID = self.call?.callInfo?.callId {
            //Hangup the previous call if there's one active
            executeEndCallAction(uuid: currentCallUUID)
        }

        self.call = call

        // Get the caller information
        let calleer = self.call?.callInfo?.callerName ?? self.call?.callInfo?.callerNumber ?? "Unknown"

        // Report the incoming call to CallKit
        let callHandle = CXHandle(type: .generic, value: calleer)
        let callUpdate = CXCallUpdate()
        callUpdate.remoteHandle = callHandle
        callUpdate.hasVideo = false

        self.callKitProvider.reportNewIncomingCall(with: incomingCallUUID, update: callUpdate) { error in
            if let error = error {
                print("Error reporting the incoming call to CallKit: \(error.localizedDescription)")
            } else {
                print("Incoming call successfully reported.")
            }
        }
    }

    func onRemoteCallEnded(callId: UUID) {
        self.executeEndCallAction(uuid: callId)
    }

     // This method will be fired when receiving a call after connecting the client from a Push Notification
    func onPushCall(call: Call) {
        // Once the notification has been received and you have advised the Telnyx SDK about it.
        // You will receive the new call here.
        self.call = call
    }
}

Setup CallKit

Use CallKit to integrate your calling services with other call-related apps on the system. CallKit provides the calling interface, and you handle the back-end communication with your VoIP service. For incoming and outgoing calls, CallKit displays the same interfaces as the Phone app, giving your app a more native look and feel. CallKit also responds appropriately to system-level behaviors such as Do Not Disturb.

For more information check the CallKit official documentation.

  1. Setup the CallKit provider and Controller
    var callKitProvider: CXProvider!
    let callKitCallController = CXCallController()
func initCallKit() {
        let configuration = CXProviderConfiguration()
        configuration.maximumCallGroups = 1
        configuration.maximumCallsPerCallGroup = 1
        self.callKitProvider = CXProvider(configuration: configuration)
        self.callKitProvider.setDelegate(self, queue: nil)
}
  1. Implement the CXProviderDelegate
extension ViewController: CXProviderDelegate {

    func providerDidReset(_ provider: CXProvider) {}

    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        let callUUID = action.callUUID
        self.call = try! self.client.newCall(callerName: self.kCallerName,
                                             callerNumber: self.kCallerNumber,
                                             destinationNumber: self.kDestination,
                                             callId: callUUID)

        // This step is important: It will fire the didActivate audioSession
        // If we skip this process the audio is not going to flow
        provider.reportOutgoingCall(with: callUUID, connectedAt: Date())
    }

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        self.call?.answer()
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        self.call?.hangup()
        self.call = nil

        action.fulfill()
    }

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        self.client.isAudioDeviceEnabled = true
    }

    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        self.client.isAudioDeviceEnabled = false
    }

}

Setup PushKit

If your app provides Voice-over-IP (VoIP) phone services, you may use PushKit to handle incoming calls on user devices. PushKit provides an efficient way to manage calls that doesn’t require your app to be running to receive calls. Upon receiving the notification, the device wakes up your app and gives it time to notify the user and connect to your call service.

For more information check the PushKit official documentation.

  1. Init PushKit to receive VoIP push notifications:
private var pushRegistry = PKPushRegistry.init(queue: DispatchQueue.main)
 func initPushKit() {
        self.pushRegistry.delegate = self
        self.pushRegistry.desiredPushTypes = Set([.voIP])
 }
  1. Implement the PKPushRegistryDelegate. Inside this delegate you will receive updates from APNS of the device Push Token and the
extension ViewController: PKPushRegistryDelegate {

    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
       // Here you will receive the APNS device token.
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        // Invalidate the old token.
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
       // Process the incoming VoIP push notifications
    }
}
  1. Inside the didUpdate credentials delegate method it's required to:
    • Store your APNS token
    • Register the APNS device token into our backend by connecting the TelnyxClient as follows:
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
        if (type == .voIP) {
            // This push notification token has to be sent to Telnyx when connecting the Client.
            let deviceToken = credentials.token.reduce("", {$0 + String(format: "%02X", $1) })
            self.savePushToken(pushToken: deviceToken)

            // Create the SDK configuration to use SIP credentials
            let txConfig = TxConfig(sipUser: self.kSipUserName,
                                    password: self.kSipUserPassword,
                                    // Notice that we are passing the push device token here to register the device
                                    pushDeviceToken: self.getPushToken())

            // Register to listen SDK events
            self.client.delegate = self

            // Connect the client
            try! client.connect(txConfig: txConfig)

            debugPrint("Your APNS token is: \(deviceToken)")
        }
    }
  1. Process the incoming push notification. In this step we need to:
    • Decode the push payload
    • Send the push notificaiton payload to the SDK to get the call
    • Register the call into the system using CallKit.
// Call this function into the PushKit delegate method `didReceiveIncomingPushWith payload`
func handleVoIPPushNotification(payload: PKPushPayload) {
        if let metadata = payload.dictionaryPayload["metadata"] as? [String: Any],
           let callId = metadata["call_id"] as? String,
           let callUUID = UUID(uuidString: callId),
           let callerName = metadata["caller_name"] as? String,
           let callerNumber = metadata["caller_number"] as? String {

            // Advise the Telnyx Client that a PN has been received passing your login credentials
            let txConfig = TxConfig(sipUser: self.kSipUserName,
                                    password: self.kSipUserPassword,
                                    pushDeviceToken: self.getPushToken())

            try! client.processVoIPNotification(txConfig: txConfig)

            // Report the incoming call to CallKit
            let callHandle = CXHandle(type: .generic, value: callerName.isEmpty ? callerNumber : callerName)
            let callUpdate = CXCallUpdate()
            callUpdate.remoteHandle = callHandle
            callUpdate.hasVideo = false

            self.callKitProvider.reportNewIncomingCall(with: callUUID, update: callUpdate) { error in
                if let error = error {
                    print("Error reporting the incoming call to CallKit: \(error.localizedDescription)")
                } else {
                    print("Incoming call successfully reported.")
                }
            }
        } else {
            debugPrint("Invalid push notification payload.")
        }
    }

Code flow summarized

  1. Initialize push kit to get an APNS token
  2. Send the APNS token to register the device into Telnyx backend by login in through the SDK using the user's SIP credentials.
  3. When a PN is received through PushKit, two main actions must be executed: a. Warn the Telnyx SDK about the incoming push notification by calling processVoIPNotification in order to get the call connected. b. Register the incoming call into CallKit. This will trigger the Incoming call system notification to be displayed.
  4. The SDK will fire the delegate method onPushCall with the new instance of the call that can be answered or rejected.
Was this page helpful?