Make a phone call

How to Make Phone Calls with the Nexmo Client SDK in JavaScript

In this guide, you'll learn how to place a phone call from a Nexmo application to a phone device (PSTN) by implementing a webhook and linking that to a Nexmo application.

You will create an app to place a call. The app will log in a user called Jane. After logging in, Jane is able to place a call as well as to end it.

Nexmo Concepts

Before proceeding any further, here are couple of concepts that you'll need to understand.

A Nexmo application allows you to easily use Nexmo products, in this case the Voice API to build voice applications in the Cloud.

A Nexmo application requires two URLs as parameters:

  • answer_url - Nexmo will make a request to this URL as soon as someone makes a call to your Nexmo number. It contains the actions that will happen throughout the call.
  • event_url - Nexmo sends event information asynchronously to this URL when the call status changes; this ultimately depicts the flow of the call.

Both URLs need to return JSON and follow the Nexmo Call Control Object (NCCO) reference. In the example below, you will define an NCCO that reads a predefined text for an incoming call, using the Text to Speech engine.

A Nexmo virtual number will be associated with the app and serve as the "entry point" to it - this is the number you'll call to test the application.

For more information on Nexmo applications please visit the Nexmo API Reference.)

Prerequisites

Application webhook

For your application to place a phone call, you'll need to provide a URL as the Answer URL webhook. For the purpose of this tutorial, you will create a gist with the content below:

[
    {
        "action": "talk",
        "text": "Please wait while we connect you."
    },
    {
        "action": "connect",
        "timeout": 20,
        "from": "YOUR_NEXMO_NUMBER",
        "endpoint": [
            {
                "type": "phone",
                "number": "CALLEE_PHONE_NUMBER"
            }
        ]
    }
]

Do not forget to replace YOUR_NEXMO_NUMBER and CALLEE_PHONE_NUMBER with the relevant values for your app.

Once created, add the gist raw URL (make sure you're using the raw version) to your Nexmo dashboard. To do this, navigate to applications, select your application and click the 'Edit' button. Set the application's Answer URL and click 'Save changes'.

You will need to repeat this process every time you're changing the gist as a new revision (with the new raw URL) is being created.

Note: The gist you created is specific to this tutorial. In a real-life scenario, the Answer URL should be provided by a purposely built web solution. Your backend should provide that can serve custom NCCOs and, for this case, receive and validate the phone number dialled from the app.

Adding the Nexmo Client to your application

We'll start with a blank HTML page with two buttons for calling a phone and hanging up a call. Create a minimal index.html file, for example:

<html>
  <head>
    <title>Make Phone Calls</title>
    <script src="./node_modules/nexmo-client/dist/nexmoClient.js"></script>
  </head>
  <body>
    <p id="notification"></p>
    <button type="button" id="call">Call</button>
    <button type="button" id="hangup">Hang Up</button>
    <script>

    </script>
  </body>
</html>

We'll need to add the Nexmo Client to it, in order to do that we'll install it from NPM first, by running this command in the same folder as your index.html file:

$ npm install nexmo-client --save

Login

Using the Nexmo Client SDK should start with logging in to NexmoClient, using a JWT user token.

In production apps, your server would authenticate the user, and would return a correctly configured JWT to your app.

For testing and getting started purposes, you can use the Nexmo CLI to generate JWTs.

We'll update the empty <script> tag that's at the bottom of your <body> tag to login with a USER_JWT, and update the notification paragraph when the login was successful. We'll create a new NexmoClient, and then call the login method, with a string for the user JWT. That returns a promise containing an application object, and we'll use that to check we're logged in with the correct user.

<script>
  const USER_JWT = "YOUR USER JWT";

  let notification = document.getElementById("notification");

  new NexmoClient()
      .login(USER_JWT)
      .then(application => {
          notification.textContent = `You've logged in with the user ${application.me.name}`
      })
      .catch(console.log);
</script>

At this point you should already be able to run the app and see that you can login successfully with the SDK. Because the Nexmo Client uses IndexedDB for its sync mechanism, we suggest you don't serve index.html using the file:// protocol. That corrupts the IndexedDB for any other users and makes the SDK behave inconsistently. You can use any HTTP server, like http-server. If you want to install it from NPM and then run it with cache disabled, here are the terminal commands:

$ npm install -g http-server
$ http-server -c-1

Make a call

In order to make a call, you'll have to use the callServer() method on the application object. The application object is returned by the login() promise. The method requires a string as parameter that it passes along to your answer_url, but since ours is a static file, we'll leave the method parameter empty.

In this example, where you're intending to dial a number, the string is the number you wish to call. It must match the number in the static file you've created.

new NexmoClient()
    .login(USER_JWT)
    .then(application => {
        ...
        document.getElementById("call").addEventListener("click", () => {
          application.callServer(callee);
          notification.textContent = `You're calling a phone number`;
        })
    })
    .catch(console.log);

Note: Even though you are specifying the callee phone number, ultimately, the number that is actually called is the one supplied in the answer_url webhook.

In a real-life scenario, you would create a server component to serve as the answer_url. When the app will place the call, the callee phone number will be sent, by the Nexmo servers, as part of the payload on the answer_url request, the backend would then validate it and supply it in the NCCO returned.

Listen for call events

When the application makes a call, you can start listening for member:call events on the application. That's going to return a call object, so you can start interacting with the call later on.

new NexmoClient()
    .login(USER_JWT)
    .then(application => {
        ...
        application.on("member:call", (member, call) => {
          notification.textContent = `You're receiving a call`;
        })
    })
    .catch(console.log);

The listener method receives a member object that contains information about who's calling, and a call object, that lets you interact with the call in progress.

Hangup a call

You can hang up a call after you've initiated it by using the hangUp() method on the call object.

new NexmoClient()
    .login(USER_JWT)
    .then(application => {
        ...
        application.on("member:call", (member, call) => {
          notification.textContent = `You're receiving a call`;
          ...
          document.getElementById("hangup").addEventListener("click", () => {
            call.hangUp();
            notification.textContent = "The call has ended";
          })
        })
    })
    .catch(console.log);

Conclusion

You have implemented your first App to Phone Voice application with the Nexmo Client SDK for JavaScript.

Open the webpage in a browser to see that you can make a call and hangup a call to a phone number. If you've followed along this tutorial, your code should look similar to this.

How to Make Phone Calls with the Nexmo Client SDK on Android

In this guide, you'll learn how to place a phone call from a Nexmo application to a phone device (PSTN) by implementing a webhook and linking that to a Nexmo application.

You will create an app to place a call. The app will log in a user called Jane. After logging in, Jane is able to place a call as well as to end it.

Nexmo Concepts

Before proceeding any further, here are couple of concepts that you'll need to understand.

A Nexmo application allows you to easily use Nexmo products, in this case the Voice API to build voice applications in the Cloud.

A Nexmo application requires two URLs as parameters:

  • answer_url - Nexmo will make a request to this URL as soon as someone makes a call to your Nexmo number. It contains the actions that will happen throughout the call.
  • event_url - Nexmo sends event information asynchronously to this URL when the call status changes; this ultimately depicts the flow of the call.

Both URLs need to return JSON and follow the Nexmo Call Control Object (NCCO) reference. In the example below, you will define an NCCO that reads a predefined text for an incoming call, using the Text to Speech engine.

A Nexmo virtual number will be associated with the app and serve as the "entry point" to it - this is the number you'll call to test the application.

For more information on Nexmo applications please visit the Nexmo API Reference.)

Prerequisites

Application webhook

For your application to place a phone call, you'll need to provide a URL as the Answer URL webhook. For the purpose of this tutorial, you will create a gist with the content below:

[
    {
        "action": "talk",
        "text": "Please wait while we connect you."
    },
    {
        "action": "connect",
        "timeout": 20,
        "from": "YOUR_NEXMO_NUMBER",
        "endpoint": [
            {
                "type": "phone",
                "number": "CALLEE_PHONE_NUMBER"
            }
        ]
    }
]

Do not forget to replace YOUR_NEXMO_NUMBER and CALLEE_PHONE_NUMBER with the relevant values for your app.

Once created, add the gist raw URL (make sure you're using the raw version) to your Nexmo dashboard. To do this, navigate to applications, select your application and click the 'Edit' button. Set the application's Answer URL and click 'Save changes'.

You will need to repeat this process every time you're changing the gist as a new revision (with the new raw URL) is being created.

Note: The gist you created is specific to this tutorial. In a real-life scenario, the Answer URL should be provided by a purposely built web solution. Your backend should provide that can serve custom NCCOs and, for this case, receive and validate the phone number dialled from the app.

The starter project

Clone or download the GitHub repository on either Kotlin orJava. On that repository you'll find two apps:

  • GetStartedCalls-Start - if you want to follow along and add the code with this tutorials
  • GetStartedCalls-Complete - if you want to look at the final result

Open the NexmoHelper class.

  1. Make sure that in enabledFeatures you have to Features.IN_APP_to_PHONE. You can remove the rest, if you haven't completed their tutorials yet.

  2. Replace the user IDs and tokens:

val USER_ID_JANE = "USR-XXX"; //TODO("replace with the UserId you generated for Jane")
val JWT_JANE = "PLACEHOLDER"; //TODO("replace with the JWT you generated for Jane")
String USER_ID_JANE = "USR-XXX"; //TODO: replace with the UserId you generated for Jane
String JWT_JANE = "PLACEHOLDER";//TODO: replace with the JWT you generated for Jane

Login

To start using the Nexmo Client SDK you need to log in to NexmoClient, using a JWT user token.

In production apps, your server would authenticate the user, and would return a correctly configured JWT to your app.

For testing and getting started purposes, you can use the Nexmo CLI to generate JWTs.

Open LoginActivity. It already has a button handler:onLoginJaneClick(...) that calls loginToSdk(...) method, with the jwt you provided.

When the login is successful, the logged in NexmoUser returns. For convenience, save a reference to NexmoUser on NexmoHelper, and then, start MainActivity.

Complete the loginToSdk() method implementation:

fun loginToSdk(token: String) {
        NexmoClient.get().login(token, object : NexmoRequestListener<NexmoUser> {

            override fun onError(nexmoApiError: NexmoApiError) {
                notifyError(nexmoApiError)
            }

            override fun onSuccess(user: NexmoUser?) {
                currentUser = user

                val intent = Intent(baseContext, MainActivity::class.java)
                startActivity(intent)
                finish()
            }
        })
    }
}
void loginToSdk(String token) {
    NexmoClient.get().login(token, new NexmoRequestListener<NexmoUser>() {

        @Override
        public void onError(NexmoApiError nexmoApiError) {}

        @Override
        public void onSuccess(NexmoUser user) {
            NexmoHelper.user = user;

            Intent intent = new Intent(getBaseContext(), MainActivity.class);
            startActivity(intent);
            finish();
        }
    });
}

At this point you should already be able to run the app and see that you can login successfully with the SDK.

Start a call

You can now make an App-to-Phone call.

Open MainActivity and complete the prepared onPhoneCallClick() handler:

fun onPhoneCallClick(view: View) {
        val callee = CALLEE_PHONE_NUMBER //TODO: swap with your phone number
        NexmoClient.get().call(callee, NexmoCallHandler.SERVER, callListener)
    }
public void onPhoneCallClick(View view) {
    String callee = CALLEE_PHONE_NUMBER; //TODO: swap with your phone number
    NexmoClient.get().call(callee, NexmoCallHandler.SERVER, callListener);
}

You are expected to replace CALLEE_PHONE_NUMBER with the number to be called. But, ultimately, the number that is actually called is the one supplied in the Answer URL webhook. In a real-life use case, you would create a server component to serve as the Answer URL. The app will send to your backend, through the Answer URL the CALLEE_PHONE_NUMBER, the backend would validate it and then supply it in the JSON returned.

Note: Whilst the default HTTP method for the Answer URL is GET, POST can also be used.

Answer a call

Once a incoming call is received, it can be answered. Open IncomingCallActivity, and complete the prepared onAnswer() button handler, to start OnCallActivity after a successful answer:

fun onAnswer(view: View) {
    currentCall?.answer(object : NexmoRequestListener<NexmoCall> {
        override fun onError(nexmoApiError: NexmoApiError) {}

        override fun onSuccess(call: NexmoCall) {
            startActivity(Intent(this@IncomingCallActivity, OnCallActivity::class.java))
            finish()
        }
    })
}
public void onAnswer(View view) {
    NexmoHelper.currentCall.answer(new NexmoRequestListener<NexmoCall>() {
        @Override
        public void onError(NexmoApiError nexmoApiError) { }

        @Override
        public void onSuccess(NexmoCall call) {
            startActivity(new Intent(IncomingCallActivity.this, OnCallActivity.class));
            finish();
        }
    });
}

Hangup

The onHangup() handler allows to reject the call. Complete the implementation in IncomingCallActivity to finish the activity:

currentCall?.hangup(object : NexmoRequestListener<NexmoCall> {
    override fun onError(nexmoApiError: NexmoApiError) {}

    override fun onSuccess(call: NexmoCall) {
        startActivity(Intent(this@IncomingCallActivity, OnCallActivity::class.java))
        finish()
    }
})
public void onHangup(View view) {
    NexmoHelper.currentCall.hangup(new NexmoRequestListener<NexmoCall>() {
        @Override
        public void onError(NexmoApiError nexmoApiError) { }

        @Override
        public void onSuccess(NexmoCall call) {
            finish();
        }
    });
}

Register to call status

To be aware of the call status, for example, if one of the members answers or hangs up, you should register to CallEvents. The FinishOnCallEnd is a NexmoCallEventListener that finishes the current activity if the call is completed or canceled.

Register to its instance, to address the use cases mentioned previously.

On both OnCallActivity and IncomingCallActivity, add:

var callEventListener = FinishOnCallEnd(this)

override fun onCreate(savedInstanceState: Bundle?) {
    currentCall?.addCallEventListener(callEventListener)
}


override fun onDestroy() {
    currentCall?.removeCallEventListener(callEventListener)
    super.onDestroy()
}
NexmoCallEventListener callEventListener = new FinishOnCallEnd(this);

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    NexmoHelper.currentCall.addCallEventListener(callEventListener);
}


@Override
protected void onDestroy() {
    NexmoHelper.currentCall.removeCallEventListener(callEventListener);
    super.onDestroy();
}

Handle permissions

For devices running Android 6.0 (API level 23) and higher, creation and operation of calls requires requesting runtime permissions. To simplify the implementation in this tutorial, BaseActivity checks the permissions in every Activity's onStart() and onStop().

To read more about the permissions required, see the setup tutorial.

Conclusion

Congratulations! You have implemented your first Phone to App Voice application with the Nexmo Client SDK for Android.

Run the app on a simulator or a device, and with another device call the Nexmo Number you linked. Then, see that you can answer, reject and hangup a call received on the phone number associated with your Nexmo application.

How to Make Phone Calls with the Nexmo Client SDK on iOS

In this guide, you'll learn how to place a phone call from a Nexmo application to a phone device (PSTN) by implementing a webhook and linking that to a Nexmo application.

You will create an app to place a call. The app will log in a user called Jane. After logging in, Jane is able to place a call as well as to end it.

Nexmo Concepts

Before proceeding any further, here are couple of concepts that you'll need to understand.

A Nexmo application allows you to easily use Nexmo products, in this case the Voice API to build voice applications in the Cloud.

A Nexmo application requires two URLs as parameters:

  • answer_url - Nexmo will make a request to this URL as soon as someone makes a call to your Nexmo number. It contains the actions that will happen throughout the call.
  • event_url - Nexmo sends event information asynchronously to this URL when the call status changes; this ultimately depicts the flow of the call.

Both URLs need to return JSON and follow the Nexmo Call Control Object (NCCO) reference. In the example below, you will define an NCCO that reads a predefined text for an incoming call, using the Text to Speech engine.

A Nexmo virtual number will be associated with the app and serve as the "entry point" to it - this is the number you'll call to test the application.

For more information on Nexmo applications please visit the Nexmo API Reference.)

Prerequisites

Application webhook

For your application to place a phone call, you'll need to provide a URL as the Answer URL webhook. For the purpose of this tutorial, you will create a gist with the content below:

[
    {
        "action": "talk",
        "text": "Please wait while we connect you."
    },
    {
        "action": "connect",
        "timeout": 20,
        "from": "YOUR_NEXMO_NUMBER",
        "endpoint": [
            {
                "type": "phone",
                "number": "CALLEE_PHONE_NUMBER"
            }
        ]
    }
]

Do not forget to replace YOUR_NEXMO_NUMBER and CALLEE_PHONE_NUMBER with the relevant values for your app.

Once created, add the gist raw URL (make sure you're using the raw version) to your Nexmo dashboard. To do this, navigate to applications, select your application and click the 'Edit' button. Set the application's Answer URL and click 'Save changes'.

You will need to repeat this process every time you're changing the gist as a new revision (with the new raw URL) is being created.

Note: The gist you created is specific to this tutorial. In a real-life scenario, the Answer URL should be provided by a purposely built web solution. Your backend should provide that can serve custom NCCOs and, for this case, receive and validate the phone number dialled from the app.

The Starter Project

Clone this Github project.

Using the Github project you cloned, in the Start folder, open GettingStarted.xcworkspace. Then, within XCode:

  1. Open Constants.swift file and add a jwt for Jane:
    var jwt: String {
        switch self {
        case .jane:
            return "" //TODO: swap with a token for Jane
        ...

as well as a phone number to call:

    static let calleePhoneNumber = "" //TODO: swap with a phone number to call
  1. From the Make-phone-call group, open MakePhoneCallViewController.swift file and make sure the following lines exist:
  • import NexmoClient - imports the sdk
  • let user = User.jane - sets the user that places the call
  • var client: NXMClient? - property for the client instance
  • var call: NXMCall? - property for the call instance

Clone this Github project.

Using the Github project you cloned, in the Start folder, open GettingStarted.xcworkspace. Then, within XCode:

  1. Open User.h file and replace the user id and token:
#define kJaneUUID @"" //TODO: swap with a user uuid for Jane

#define kJaneJWT @"" //TODO: swap with a token for Jane
  1. From the Make-phone-call group, open MakePhoneCallViewController.m file and make sure the following lines exist:
  • #import <NexmoClient/NexmoClient.h> - imports the sdk
  • @property User *user; - sets the user that places the call
  • @property NXMClient *client; - property for the client instance
  • @property NXMCall *call; - property for the call instance

Login

Using the Nexmo Client SDK should start with logging in to NexmoClient, using a jwt user token.

In production apps, your server would authenticate the user, and would return a correctly configured JWT to your app.

For testing and getting started purposes, you can use the Nexmo CLI to generate JWTs.

Inside MakePhoneCallViewController, explore the setup methods that were written for you on viewDidLoad.

Now locate the following line //MARK: Setup Nexmo Client and complete the setupNexmoClient method implementation:

func setupNexmoClient() {
    client.setDelegate(self)
    client.login(withAuthToken: user.jwt)
}

Notice that self is set to be the delegate for NXMClient. Do not forget to adopt the NXMClientDelegate protocol and implement the required methods.

Add the required protocol adoption declaration to the class extension located towards the end of the MakePhoneCallViewController.swift file, under the //MARK:- Client Delegate line:

extension MakePhoneCallViewController: NXMClientDelegate {
    ...
}

The client(_:didChanged:reason:) methods of the NXMClientDelegate protocol indicates if the login was successful and you can start using the SDK.

Add the following methods inside the extension.

extension MakePhoneCallViewController: NXMClientDelegate {

    func client(_ client: NXMClient, didChange status: NXMConnectionStatus, reason: NXMConnectionStatusReason) {
        updateInterface()
    }

    func client(_ client: NXMClient, didReceiveError error: Error) {
        updateInterface()
    }

}

Inside MakePhoneCallViewController, explore the setup methods that were written for you on viewDidLoad.

Now locate the following line //MARK: Setup Nexmo Client and complete the setupNexmoClient method implementation:

- (void)setupNexmoClient {
    self.client = [NXMClient shared];
    [self.client setDelegate:self];
    [self.client loginWithAuthToken:self.user.jwt];
}

Notice that self is set to be the delegate for NXMClient. Do not forget to adopt the NXMClientDelegate protocol and implement the required methods.

Add the required protocol methods, under the //MARK:- NXMClientDelegate line:

- (void)client:(nonnull NXMClient *)client didChangeConnectionStatus:(NXMConnectionStatus)status reason:(NXMConnectionStatusReason)reason {
    [self updateInterface];
}

- (void)client:(nonnull NXMClient *)client didReceiveError:(nonnull NSError *)error {
    [self updateInterface];
}

At this point you should already be able to run the app and see that you can login successfully with the SDK.

Start a call

You can now make an App-to-Phone call.

The Call button press is already connected to MakePhoneCallViewController.

Implement the callNumber: method to start a call.

@IBAction func callNumber(_ sender: Any) {
    // call initiated but not yet active
    if callStatus == .initiated && call == nil {
        callStatus = .unknown
        self.call = nil
        updateInterface()
        return
    }
    // start a new call (check if a call already exists)
    guard let call = call else {
        startCall()
        return
    }
    end(call: call)
}

If a call is already in progress, taping the button will end it.

Implement startCall - it will start the call, and also update the interface to show that a call is in progress:

private func startCall() {
    callStatus = .initiated
    client.call(User.calleePhoneNumber, callHandler: .server) { [weak self] (error, call) in
        guard let self = self else { return }
        // Handle create call failure
        guard let call = call else {
            if let error = error {
                // Handle create call failure
                print("✆  ‼️ call not created: \(error.localizedDescription)")
            } else {
                // Handle unexpected create call failure
                print("✆  ‼️ call not created: unknown error")
            }
            self.callStatus = .error
            self.call = nil
            self.updateInterface()
            return
        }

        // Handle call created successfully.
        // callDelegate's  statusChanged: will be invoked with needed updates.
        call.setDelegate(self)
        self.call = call
        self.updateInterface()
    }
    updateInterface()
}

Implement the call: method to start a call.

- (IBAction)call:(id)sender {
    if (self.call != nil || self.callStatus == CallStatusInitiated) {
        [self endCall];
    } else {
        [self startCall];
    }
}

If a call is already in progress, taping the button will end it.

Implement the startCall method to start a call. It will start the call, and also update the interface to show that a call is in progress:

- (void)startCall {
    self.callStatus = CallStatusInitiated;
    __weak MakePhoneCallViewController *weakSelf = self;
    [self.client call:kCalleePhoneNumber callHandler:NXMCallHandlerServer completionHandler:^(NSError * _Nullable error, NXMCall * _Nullable call) {
        if(error) {
            NSLog(@"✆  ‼️ call not created: %@", error);
            weakSelf.call = nil;
            [weakSelf updateInterface];
            return;
        }
        NSLog(@"✆  call: %@", call);
        weakSelf.call = call;
        [weakSelf.call setDelegate:self];
        [weakSelf updateInterface];
    }];
}

Even though you are specifying the callee phone number, ultimately, the number that is actually called is the one supplied in the Answer URL webhook. In a real-life use case, you would create a server component to serve as the Answer URL. The app will send to your backend, through the Answer URL the CALLEE_PHONE_NUMBER, the backend would validate it and then supply it in the JSON returned.

Note: Whilst the default HTTP method for the Answer URL is GET, POST can also be used.

Call Handler

Note the use of NXMCallHandler.server as the callHandler in the client's call: method above; this specifies that the logic of the call is defined by the server - a requirement for outbound PSTN calls.

The other callHandler is NXMCallHandler.inApp, useful for making simple calls as shown in this tutorial.

client?.call(callee, callHandler: .inApp) { [weak self] (error, call) in
    ...
}
[self.nexmoClient call:@callee callHandler:NXMCallHandlerInApp completion:^(NSError * _Nullable error, NXMCall * _Nullable call) {
    ...
}];

Call Delegate

Note that, when a call is placed successfully, we're setting self as the delegate for it.

We'll now adopt the NXMCallDelegate as an extension on MakePhoneCallViewController, under the //MARK:- NXMCallDelegate line:

extension MakePhoneCallViewController: NXMCallDelegate {

}

Copy the following NXMCallDelegate methods inside the extension:

func call(_ call: NXMCall, didUpdate callMember: NXMCallMember, with status: NXMCallMemberStatus) {

    // call ended
    if call.myCallMember == callMember, status == .completed {
        self.callStatus = .completed
        self.call?.hangup()
        self.call = nil
    }

    // call error
    if call.otherCallMembers.contains(callMember), [NXMCallMemberStatus.failed, NXMCallMemberStatus.busy, NXMCallMemberStatus.timeout].contains(callMember.status) {
        self.callStatus = .rejected
        self.call?.hangup()
        self.call = nil
    }

    // call rejected
    if call.otherCallMembers.contains(callMember), callMember.status == .rejected {
        self.callStatus = .rejected
        self.call?.hangup()
        self.call = nil
    }

    // call ended
    if call.otherCallMembers.contains(callMember), callMember.status == .completed {
        self.callStatus = .completed
        self.call?.hangup()
        self.call = nil
    }

    updateInterface()

}

func call(_ call: NXMCall, didUpdate callMember: NXMCallMember, isMuted muted: Bool) {
    updateInterface()
}

func call(_ call: NXMCall, didReceive error: Error) {
    updateInterface()
}

As with NXMClient, NXMCall also has a delegate. Add the required protocol methods under the //MARK:- NXMCallDelegate line:

- (void)call:(nonnull NXMCall *)call didUpdate:(nonnull NXMCallMember *)callMember withStatus:(NXMCallMemberStatus)status {
    NSLog(@"✆  🤙 Call Status update | member: %@ | status: %@", callMember.user.displayName, callMemberStatusDescriptionFor(status));

    // call completed
    if (status == NXMCallMemberStatusCanceled || status == NXMCallMemberStatusCompleted) {
        self.callStatus = CallStatusCompleted;
        [self.call hangup];
        self.call = nil;
    }

    // call error
    if ( (call.myCallMember.memberId != callMember.memberId) && (status == NXMCallMemberStatusFailed || status == NXMCallMemberStatusBusy)) {
        self.callStatus = CallStatusError;
        [self.call hangup];
        self.call = nil;
    }

    // call rejected
    if ( (call.myCallMember.memberId != callMember.memberId) && (status == NXMCallMemberStatusRejected)) {
        self.callStatus = CallStatusRejected;
        [self.call hangup];
        self.call = nil;
    }

    [self updateInterface];
}

- (void)call:(nonnull NXMCall *)call didUpdate:(nonnull NXMCallMember *)callMember isMuted:(BOOL)muted {
    [self updateInterface];
}

- (void)call:(nonnull NXMCall *)call didReceive:(nonnull NSError *)error {
    NSLog(@"✆  ‼️ call error: %@", [error localizedDescription]);
    [self updateInterface];
}

The call(_:didUpdate:with:) method notifies on changes that happens to members on the call.

Hangup a call

Once the "End Call" button is pressed, it is time to hangup the call.

Implement the private end: method and call hangup for myCallMember.

private func end(call: NXMCall) {
    call.hangup()
}

Updates for callMember statuses are received in call(_:didUpdate:with:) as part of the NXMCallDelegate as you have seen before.

The existing implementation is already handling call hangup.

Implement endCall method and call hangup for call.

- (void)endCall {
    [self.call hangup];
    [self updateInterface];
}

Handle permissions

For the call to happen, Audio Permissions are required. In the appDelegate of the sample project, you can find an implementation for the permissions request in application:didFinishLaunchingWithOptions.

To read more about the permissions required, see the setup tutorial.

Conclusion

You have implemented your first App to Phone Voice application with the Nexmo Client SDK for iOS.

Run the app on a simulator and see that you can place and hangup a call to a PSTN phone number from the phone number associated with your Nexmo application.

If possible, test on a device using your developer signing and provisioning facility.