Skip to content

Webex Calling using Mobile SDKs

Ravi Chandra Sekhar Sarika edited this page Oct 16, 2023 · 13 revisions

Webex Calling using Mobile SDKs

Webex Calling is a cloud-based phone system offered as part of Webex by Cisco that enables users to make and receive calls. With the new webex mobile SDK it is now possible to add calling capabilities to your own app.

This feature is available in SDK version 3.9.1 onwards.

License requirements

A Webex Calling Professional license is required for using webex calling features in the mobile SDK. Please contact sales at https://pricing.webex.com/in/en/ or our support team at https://developer.webex.com/support for more information.

Prerequisites

Make sure you meet the following prerequisites before using the SDK calling features:

  • Register an app in the Developer Portal as described in the following link.
  • Follow the steps to integrate the SDK into your Android app as described in the following link.
  • For Webex Mobile SDK initialization and OAuth-based login configuration, refer to the first example in this link.
  • Incoming calls for Webex Calling are delivered through webhook for mobile apps with SDK integrations. Org admin can create a firehose webhook with resource as "telephony_push" and event as "updated". Once a webhook payload is received in customer's backend server it should be extracted and passed on to mobile app for further processing. Contents that needs to be passed to mobile app is detailed in further section. Normally these are delivered from customer's backend server to their mobile app integrating SDK through APNS(Apple Push Notification Service) or FCM (Firebase Cloud Messaging)

Note: Webhook events on telephony_push would be available shortly

Basic usage guide

1. Registering for callbacks

To be able to make and receive calls, the phone services needs to be ready. Once a user successfully signs into Webex, the authentication for Webex calling phone services happens in the background using the Single Sign On (SSO) mechanism. To know when the phone servivces are ready, an application can register the WebexUCLoginDelegate as given below. The onUCServerConnectionStateChanged callback in the delegate provides the phone services connection status as UCLoginServerConnectionStatus.Connected when it's ready for use.

class YourViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        // You need to register your class as a delegate
        webex.ucLoginDelegate = self
    }
}

extension HomeViewController: WebexUCLoginDelegate {
    func onUCLoggedIn() {
        // login attempt was successful
    }
    
    func onUCLoginFailed() {
        // login attempt failed
    }
    
    func onUCServerConnectionStateChanged(status: UCLoginServerConnectionStatus, failureReason: PhoneServiceRegistrationFailureReason) {
        if status == .Connected {
            // server connection success
        }
    }
    
    func showUCSSOLoginView(to url: String) {
        webex.getUCSSOLoginView(parentViewController: self, ssoUrl: url) { success in
            if let success = success, success {
                // you're logged in
            }
        }
    }
    
    func showUCNonSSOLoginView() {
        webex.setCallServiceCredential(username: "[email protected]", password: "SuperSecret")
    }
}

2. Query phone services status

To query the current phone services status use the following API:

let phoneServicesStatus = webex.getUCServerConnectionStatus()
if (phoneServicesStatus == UCLoginServerConnectionStatus.Connected) {
    // Indicates that phone services are ready
}

3. Place an outgoing call

Once the phone services are ready, an outgoing call can be placed to any Webex calling or PSTN number. Once you're successfully connected to phone services, You can start making calls using the webex.phone.dialPhoneNumber api. This api should be used to dial only phone numbers. Incase of meeting links, spaces, meeting numbers, sip uris and email addresses, you can use the regular webex.phone.dial api. If you use webex.phone.dialPhoneNumberto dial anything other than phone numbers, you will get a call failure with invalidApiError. Typically, an application displays a dial pad UI to capture the phone number entered by a user. That phone number is then passed to the webex.phone.dialPhoneNumber API. The dial API, if successful, provides a Call object. Use the Call object for further actions such as hangup, hold/resume, merge, transfer and many more. There are also certain call events that are emitted during the call for which an observer can be set:

webex.phone.dialPhoneNumber("+1800123456", option: MediaOption.audioOnly()) { result in
    switch result {
    case .success(let call):
        call.onConnected = {
            // ...
        }
        call.onDisconnected = { reason in
            // ...
        }
    case .failure(let error):
        //call failure
    } 
}

4. Mute or unmute a call

To mute a call:

call?.sendingAudio = false 

To unmute a call:

call?.sendingAudio = true 

5. Hold or resume a call

To hold a call:

call.holdCall(putOnHold: true)

To resume a call:

call.holdCall(putOnHold: false)

To query the hold status of a call:

let isOnHold = call?.isOnHold()

6. Add a participant to a call

To add a participant to a call

  1. Put current active call on hold
oldCall?.holdCall(putOnHold: true)
  1. Start a call with new participant's number that can be merged later. Application needs to show a dial pad and capture the new participant's number.
call.startAssociatedCall(dialNumber: "+1800123457", associationType: .Merge, isAudioCall: true, completionHandler: { [weak self] result in
    switch result {
    case .success(let call):
        // Call association is successful
        let newCall: Call? = call
        // Store this call object
    case .failure(let error):
        // Call association failed
    }
})
  1. Merge old call with new one
// Merge call
newCall.mergeCall(targetCallId: oldCall.callId)

NOTE: Adding a participant is also supported when already 2 calls are active. This enables the user to add a participant to one of the call while other call in the queue is in hold state.

7. Assisted transfers

Suppose there is an ongoing call between user A and user B. User A wants to connect with user C and transfer the call so that user B and user C remain in the call while user A drops from the call after call gets connected. This type of transfer is called an assisted transfer. Initiate an assisted transfer using the same call?.startAssociatedCall APIs.

  1. Put current active call on hold
oldCall?.holdCall(putOnHold: true)
  1. Start a call with new participant's number that can be transferred later. Application needs to show a dial pad and capture the new participant's number.
call.startAssociatedCall(dialNumber: "+1800123457", associationType: .Transfer, isAudioCall: true, completionHandler: { [weak self] result in
    switch result {
    case .success(let call):
        // Call association is successful
        let newCall: Call? = call
        // Store this call object
    case .failure(let error):
        // Call association failed
    }
})
  1. Transfer the call to new participant
// Transfer call
oldCall.transferCall(toCallId: newCall.callId)

NOTE: Assisted transfer is also supported when already 2 calls are active. This enables the user to consult transfer one of the calls and resume the other call in queue.

8. Direct transfers

There is also an option to blindly transfer a call to another participant without the call being established with new participant. This type of transfer is called a direct transfer. The application needs to capture the new number from the user and call the direct transfer API.

call?.directTransferCall(toPhoneNumber: phoneNumber, completionHandler: { err in
    if err == nil {
        // Direct transfer  is successful
    }
    else
    {
        // Direct transfer failed
    }
})

9. End a call

To end a call, call.hangup can be used. A call must be connected before it can be hanged up.

call.hangup(completionHandler: { error in
    if error == nil {
        // Call ended successful
    }
    else {
        // Call end failed
    }
})

Incoming call handling

Webex calling for incoming calls is designed to be delivered for SDK integrations through webhook. By registering a webhook, developer can specify a targetUrl of their server backend that can receive and dispatch required payload to mobile apps. Typically, APNS or FCM push channels are used to deliver the payload. This enables the app to retrieve incoming calls when the app is in the foreground, background, or killed state. The following diagram gives an overview on how incoming call notifications are delivered: Webex Calling Incoming call overview

Below are the steps that outline how to get a Webex calling payload through webhook through developer's backend. APNS or FCM push channel is assumed for illustration.

    {
    "name": "Webex Calling XYZ Firehose",
    "targetUrl": "https://example.com/message-events", // Server url of backend server that can receive the webhook notification
    "resource": "telephony_push",
    "event": "updated",
    "ownedBy" : "org"
    }
  • After successful login into mobile app that integrates SDK, application would send the Person Id and APNS or FCM token to their backend server and store it. Person Id would be required to look up a webex user when backend gets a webhook notification and push tokens is used to dispatch push notification. getMe() returns data that has personId.
    webex.people.getMe(completionHandler: { [weak self] in
        switch $0 {
        case .success(let user):
            let personId = user.encodedId
        case .failure(let error):
            //Error getting current user
        }
    })

NOTE: To setup Apple Push Notification Service on your iOS app refer this link.

To handle the VoIP type notification you'll need to setup CallKit in your app, refer this link.

  • When backend server would get a webhook notification which is a json payload for incoming call. Root level key in json payload would have resource as "telephony_push". Server would retrieve the tokens for the user for whom this call is to be notified using "actorId" field at root level of json. Server should extract the json under data->apnsPayload and data->fcmPayload and dispatch apnsPayload to iOS devices and fcmPayload to android devices if any. Payload should not be altered. For APNS a voip notification can be sent.

  • Once the application receives the FCM or APNS payload, It should be passed to the SDK API. Further steps are detailed below.

  1. Webex incoming call payload is of type data message with custom keys. These would be delivered to PKPushRegistryDelegate class didReceiveIncomingPushWith function implemented by the application. Once the message is received it needs to be set towards SDK for processing. Application needs to check if the webex SDK is initialized and authorization is successful and also set an incoming call listener before processing the message.
if type == .voIP {
    // Report the call to CallKit, and let it display the call UI.
    guard let bsft = payload.dictionaryPayload["bsft"] as? [String: Any], let sender = bsft["sender"] as? String else {
        print("payload not valid")
        return
    }
    callKitManager?.reportIncomingCallFor(uuid: UUID(), sender: sender) {
        completion()
        self.establishConnection(payload: payload)
    }
} 
webex.initialize { [weak self] success in
    if success {
        webex.phone.onIncoming = { [weak self] call in
            self?.callKitManager?.updateCall(call: call)
        }
        do {
            let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted)
            let string = String(data: data, encoding: .utf8) ?? ""
            // Received push
            webex.phone.processPushNotification(message: string) { error in
                if let error = error {
                    // processPushNotification error
                    
                }
            }
        }
        catch (let error){
            // error
        }
        
    }
}

NOTE: If the call is answered by someone else or picked up on another device, we will get a normal APNS push, which need to be passed to the processPushNotification API.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let authType = UserDefaults.standard.string(forKey: "loginType") else { return }
    if authType == "jwt" {
        // init Webex using JWT
    } else if authType == "token" {
        // init Webex using token
    } else {
        // init Webex using oauth 
    }
    DispatchQueue.main.async {
        webex.initialize { success in
            if success {
                do {
                    let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted)
                    let string = String(data: data, encoding: .utf8) ?? ""
                    // Received push
                    webex.phone.processPushNotification(message: string) { error in
                        if let error = error {
                            // processPushNotification error
                        }
                    }
                }
                catch (let error){
                    // error
                }
                
            }
        }
    }
}

The application will receive an incoming Call object in the observer Phone.IncomingCallListener set towards the SDK using webex.phone.setIncomingCallListener.

  1. The call can be answered using answer API.
call.answer(option: MediaOption.audioOnly(), completionHandler: { error in
    if error != nil {
        ...
    }
})
  1. An incoming call can be declined using reject API.
call?.reject(completionHandler: { error in
    if error == nil {
        // Reject call successful
    } else {
        // Reject call failed
    }
})

Incoming call push events

"telephony_push" events from webhook is for NewCall type notifications which signifies a new incoming call for a particular user.

You can set a Call observer to get certain call state changes.

call.onConnected = {
    // Indicates call is successfully established
}

call.onDisconnected = { reason in
    // Indicates call is disconnected
}

call.onRinging = {
    // Indicates call is placed and is in ringing state
}

call.onInfoChanged = {
    // Indicates call info is updated
    // call.externalTrackingId can be used here to retrieve tracking id.
}

A few key events are:

  1. onRinging : Indicates that a call is placed and the remote participant's device is in the ringing state. An application can play a ring tone here.
  2. onConnected : Indicates that a call is successfully established. If a ring tone was playing, the application can stop it with this event. An application can show an appropriate in-call UI.
  3. onDisconnected : Indicates call is disconnected. The CallDisconnectedEvent event param has a call object of the disconnected call. This event is also notified if the call is answered on a different device by the same user or by a different user belonging to same hunt group. In such cases, if the incoming call notification is shown by the application then it needs to be dismissed.
  4. onInfoChanged : Indicates call details or object id updated.

Incoming call sequence diagram: Incoming call sequence diagram

Limit on number of Incoming Calls

Mobile SDK supports a maximum of 2 incoming calls. 3rd incoming call is ignored/silently dimissed. At any moment only one call can be in resumed state. Typical flow in the application for 2 incoming calls is as follows:

  • Application gets an incoming call notification
  • Notification is shown to the user and user accepts the call
  • Application gets another incoming call notification
  • Notification of the new call is shown to the user
  • If user accepts the new call, existing call is put on hold and new call is answered.
    • Calls can be switched between hold and resume state as needed.
    • Calls can also be hungup using their respective call object.
    • Application needs to handle the events notified by individual CallObserver and update UI accordingly
  • If user rejects the new call, new call is rejected and existing call continues to remains active

Phone services sign in/sign out APIs

Applications can sign out of only Webex calling phone services to stop receiving and making calls while users are still able to use other functionality such as messaging and meeting.

1. Sign out of phone services

To sign out of phone services:

webex.phone.disconnectPhoneServices(completionHandler: { result in
    switch result {
    case .success:
        // PhoneServices disconnected successfully
    case .failure:
        // PhoneServices disconnection failure
    }
})

NOTE: The actual phone service status is notified in onUCServerConnectionStateChanged callback.

2. Sign in of phone services

To sign in to phone services:

webex.phone.connectPhoneServices(completionHandler: { result in
    switch result {
    case .success:
        // PhoneServices connected successfully
    case .failure:
        // PhoneServices connection failure
    }
})

NOTE: The actual phone service status is notified in onUCServerConnectionStateChanged callback.

Get the calling type of a user

The SDK supports different types of calling like CUCM and WebexCalling. A user can be associated with at most one calling type. The following API can be used to check the user's calling type, in this case, WebexCalling.

if(webex.phone.getCallingType() == Phone.CallingType.WebexCalling){
    // Indicates Webex Calling is supported for signed in user
}

Get External tracking Id

To log and track metrics application can use below API to retrieve a tracking Id. Please note this is only available after onInfoChanged callback in CallObserver is notified.

    call.externalTrackingId
Clone this wiki locally