Make outbound calls

The workflow to make a Call using the Voice API is:

Outbound call workflow

  1. You make an authenticated Call request to the Voice API.
  2. Nexmo accepts the request and sends response information.
  3. Nexmo places a call and awaits an answer.
  4. On answer, Nexmo makes a GET request to retrieve an NCCO from your answer_url webhook endpoint and follows the actions in your NCCO.
  5. Nexmo sends a POST request to your event_url webhook endpoint when the Call status changes.

For more advanced systems, depending on the input from your user, you can return an NCCO to customize their experience.

To create a Call using the Voice API:

  1. Create a Nexmo application.
  2. Create the NCCOs for the business logic you are implementing in your Voice app:

    [
      {
        "action": "talk",
        "voiceName": "Russell",
        "text": "Hi, this is Russell. You are listening to a Call made with Voice API"
      }
    ]
    
    [
      {
        "action": "record",
        "eventUrl": ["https://example.com/recordings"],
        "endOnSilence": "3"
      },
      {
        "action": "connect",
        "eventUrl": ["https://example.com/events"],
        "from":"447700900000",
        "endpoint": [
          {
            "type": "phone",
            "number": "447700900001"
          }
        ]
      }
    ]
    
    [
      {
      "talk": {
        "text": "Please enter a digit"
      },
        "action": "input",
        "eventUrl": [
          "https://example.com/ivr"
        ]
      }
    ]
    

    To run a few tests without creating an NCCO provider in the next step, use the following static NCCOs:

  3. Create the webhook endpoint that provides an NCCO:

    <?php
    
    $method = $_SERVER['REQUEST_METHOD'];
    
    switch ($method) {
      case 'GET':
        //Retrieve with the parameters in this request
        $to = $request['to']; //The endpoint being called
        $from = $request['from']; //The endpoint you are calling from
        $uuid = $request['conversation_uuid']; //The unique ID for this Call
    
        //For more advanced Conversations you use the paramaters to personalize the NCCO
        //Dynamically create the NCCO to run a conversation from your virtual number
        if( $to == "441632960960")
          $ncco='[
          {
            "action": "talk",
            "text": "Hello Russell, welcome to a Call made with Voice API"
          }
          ]';
        else
          $ncco='[
          {
            "action": "talk",
            "text": "Hello Rebekka, welcome to a Call made with Voice API"
          }
          ]';
    
        header('Content-Type: application/json');
        echo $ncco;
        break;
      default:
        //Handle your errors
        handle_error($request);
        break;
    }
    
    #To run this code, replace the MyHandler in
    #https://wiki.python.org/moin/BaseHttpServer With the following code,
    import time
    import BaseHTTPServer
    import re
    import json
    
    class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        def do_GET(s):
            #Parse parameters in the GET request
            parsed_path = urlparse(s.path)
            try:
                    params = dict(
                    [p.split('=') for p in parsed_path[4].split('&')])
            except:
                    params = {}
    
            #Retrieve with the parameters in this request
            call_to = params['to']      #The endpoint being called
            call_form = params['from']  #The endpoint you are calling from
            call_uuid = params['conversation_uuid']     #The unique ID for this Conversation
    
            #Dynamically create the NCCO to run a conversation from your virtual number
            if call_to == "441632960960":
                ncco=[
                {
                    "action": "talk",
                    "text": "Hello Russell, welcome to a Call made with Voice API"
                    }
                ]
            else:
                ncco=[
                {
                    "action": "talk",
                    "text": "Hello Rebekka, welcome to a Call made with Voice API"
                    }
                ]
    
            #For more advanced Conversations you use the paramaters to personalize the NCCO
            #Dynamically create the NCCO to run a conversation from your virtual number
    
            print "GET Request from " + s.path
            s.send_response(200)
            s.send_header("Content-type", "application/json")
            s.end_headers()
            s.wfile.write(json.dumps(ncco))
    
    require 'socket'
    require 'uri'
    require 'json'
    
    #Dynamically create the NCCO to send synthsized speech to a virtual number
    
    # Initialize a TCPServer
    def generate_ncco(request_line)
    #Parse the parameters and check if the message was delivered
      params = URI::decode_www_form(request_line).to_h
    
      #Retrieve with the parameters in this request
      to = params['to']         #The endpoint being called
      from = params['from']     #The endpoint you are calling from
      uuid = params['conversation_uuid']    #The unique ID for this Conversation
      #For more advanced Conversations you use the paramaters to personalize the NCCO
    
      #Dynamically create the NCCO to run a conversation from your virtual number
      if to == "441632960960"
        ncco=[
          {
            "action": "talk",
            "text": "Hello Russell, welcome to a Call made with Voice API"
          }
        ]
      else
        ncco=[
          {
            "action": "talk",
            "text": "Hello Rebekka, welcome to a Call made with Voice API"
          }
        ]
      end
    
      return ncco.to_json
    end
    
    server = TCPServer.new('', 9999)
    # Wait for connections
    loop do
      # Wait until a client connects
      socket = server.accept
      method, path = socket.gets.split
      resp = generate_ncco(path)
      # Return the 200 OK
    
      headers = ["HTTP/1.1 200 OK",
                 "Content-Type: application/json",
                 "Content-Length: #{resp.length}\r\n\r\n"].join("\r\n")
      socket.puts headers
      #Return the NCCO
      socket.puts resp
      # Close the socket, terminating the connection
      socket.close
    end
    
  4. Handle the call state changes at event_url. To inspect and debug state changes without writing your own server, create an online endpoint service such as requestb.in  or https://hookbin.com/  . The following code examples show you how to handle changes in Conversation state:

    <?php
    /*
     *  Place this script at event_url for your Nexmo application
     */
    $method = $_SERVER['REQUEST_METHOD'];
    // work with get or post
    $request = array_merge($_GET, $_POST);
    
    /*
     *  Do something for changed call status
    */
    function handle_call_status()
    {
      $decoded_request = json_decode(file_get_contents('php://input'), true);
      // Work with the call status
      if (isset($decoded_request['status'])) {
        switch ($decoded_request['status']) {
          case 'ringing':
              echo("Handle conversation_uuid, this return parameter identifies the Conversation");
              break;
          case 'answered':
              echo("You use the uuid returned here for all API requests on individual calls");
              break;
          case 'complete':
              //if you set eventUrl in your NCCO. The recording download URL
              //is returned in recording_url. It has the following format
              //https://api.nexmo.com/media/download?id=52343cf0-342c-45b3-a23b-ca6ccfe234b0
              //Make a GET request to this URL using a JWT as authentication to download
              //the Recording. For more information, see Recordings.
              break;
          default:
              break;
      }
          return;
      }
    }
    
    /*
     *  Handle errors
    */
    function handle_error($request){
         //code to handle your errors
    }
    
    /*
      Send the 200 OK to Nexmo and handle changes to the call
    */
    switch ($method) {
      case 'POST':
        //Retrieve your dynamically generated NCCO.
        $ncco = handle_call_status();
        header("HTTP/1.1 200 OK");
        break;
      default:
        //Handle your errors
        handle_error($request);
        break;
    }
    
    import time
    import BaseHTTPServer
    import re
    import json
    
    #To run this code, replace the MyHandler in
    #https://wiki.python.org/moin/BaseHttpServer With the following code,
    class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        def do_POST(s):
            """Parse parameters in the POST request"""
            content_len = int(s.headers.getheader('content-length', 0))
            post_body = s.rfile.read(content_len)
            inbound_message = json.loads(post_body.decode('utf-8'))
    
            # Check if your messages are successful
            if inbound_message['status'] == 'ringing':
                print ("Handle conversation_uuid, this return parameter identifies the Conversation")
            if inbound_message['status'] == 'answered':
                print ("You use the uuid returned here for all API requests on individual calls")
            if inbound_message['status'] == 'complete':
                print ("Find your recording")
                #if you set eventUrl in your NCCO. The recording download URL
                #is returned in recording_url. It has the following format
                # https://api.nexmo.com/media/download?id=52343cf0-342c-45b3-a23b-ca6ccfe234b0
                #Make a GET request to this URL using a JWT as authentication to download
                #the Recording. For more information, see Recordings.
            """Tell Nexmo that you have recieved the POST request."""
            s.send_response(200)
            s.send_header("Content-type", "text/html")
            s.end_headers()
                ]
            else:
                ncco=[
                {
                    "action": "talk",
                    "text": "Hello Rebekka, welcome to a Call made with Voice API"
                    }
                ]
    
            #For more advanced Conversations you use the paramaters to personalize the NCCO
            #Dynamically create the NCCO to run a conversation from your virtual number
    
            print "GET Request from " + s.path
            s.send_response(200)
            s.send_header("Content-type", "application/json")
            s.end_headers()
            s.wfile.write(json.dumps(ncco))
    
    require 'socket'
    require 'uri'
    require 'pstore'
    require 'JSON'
    
    def handle_inbound_message(request_line)
    
      #Parse the parameters and check if the message was delivered
      inbound_message = JSON.parse(request_line)
    
      #Check the is an inbound message
      if (inbound_message['status'] == 'ringing')
        p ("Handle the conversation_uuid return parameter that identifies this Conversation")
      elsif (inbound_message['status'] == 'answered')
        p ("YYou use the uuid returned here for all API requests on individual calls")
      elsif (inbound_message['status'] == 'complete')
        #if you set eventUrl in your NCCO. The URL to download
        #the recording from is set in recording_url.
        #To download the recording
        #curl recording_url&api_key=***&api_secret=***” -o “recording.mp3”
      end
    end
    
    # Initialize a TCPServer
    server = TCPServer.new('', 9999)
    
    # Wait for connections
    loop do
      # Wait until a client connects
      socket = server.accept
      method, path = socket.gets.split
      headers = {}
      while line = socket.gets.split(' ', 2)              # Collect HTTP headers
        break if line[0] == ""                            # Blank line means no more headers
        headers[line[0].chop] = line[1].strip             # Hash headers by type
      end
      content_length = headers["Content-Length"].to_i
      unless content_length == 0
        data = socket.read(content_length)
        handle_inbound_message(data)
      end
    
      # Return the 200 so Nexmo does not send the DLR to you repeatedly
      resp = "Thank you"
      headers = ["HTTP/1.1 200 OK",
                 "Content-Type: text/html; charset=iso-8859-1",
                 "Content-Length: #{resp.length}\r\n\r\n"].join("\r\n")
      socket.puts headers
      socket.puts resp
      # Close the socket, terminating the connection
      socket.close
    end
    
  5. Write a method to generate the JWT used to access Nexmo API for your application:

    <?php
    //This example uses a PHP library from composer require lcobucci/jwt.
    //To install: composer require lcobucci/jwt
    
    require __DIR__ . '/vendor/autoload.php';
    
    use Lcobucci\JWT\Builder;
    use Lcobucci\JWT\Signer\Key;
    use Lcobucci\JWT\Signer\Rsa\Sha256;
    
    function generate_jwt( $application_id, $keyfile) {
    
        $jwt = false;
        date_default_timezone_set('UTC');    //Set the time for UTC + 0
        $key = file_get_contents($keyfile);  //Retrieve your private key
        $signer = new Sha256();
        $privateKey = new Key($key);
    
        $jwt = (new Builder())->setIssuedAt(time() - date('Z')) // Time token was generated in UTC+0
            ->set('application_id', $application_id) // ID for the application you are working with
            ->setId( base64_encode( mt_rand (  )), true)
            ->sign($signer,  $privateKey) // Create a signature using your private key
            ->getToken(); // Retrieves the JWT
    
        return $jwt;
    }
    
    """
    requirements
    install the python package manager: https://pip.pypa.io/en/stable/installing/
    Install Python crypto: pip install crypto
    Install Python Jose: pip install python-jose
    Install Python Requests: pip install requests
    """
    
    import json
    import requests
    from datetime import datetime
    from base64 import urlsafe_b64encode
    import os
    import calendar
    from jose import jwt
    
    def generate_jwt(application_id="none", keyfile="application_secret_key.txt") :
    
        application_private_key = open(keyfile, 'r').read()
        # Add the unix time at UCT + 0
        d = datetime.utcnow()
    
        token_payload = {
            "iat": calendar.timegm(d.utctimetuple()),  # issued at
             "application_id": application_id,  # application id
             "jti": urlsafe_b64encode(os.urandom(64)).decode('utf-8')
        }
    
        # generate our token signed with this private key...
        return jwt.encode(
            claims=token_payload,
            key=application_private_key,
            algorithm='RS256')
    
    #This sample uses https://github.com/jwt/ruby-jwt
    
    require 'jwt'
    require "base64"
    require "SecureRandom"
    
    #Mint a  JWT using the application ID and private key
    
    def generate_jwt(application_id, keyfile)
    
        #Read the key from a file
        application_private_key = File.read(keyfile)
        # Add the unix time at UCT + 0 to the payload
        token_payload = {
            "iat" => Time.now.getutc.to_i,
             "application_id" => application_id,
             "jti" => Base64.encode64(SecureRandom.base64)
        }
    
        # generate our token signed with this private key...
        rsa_private = OpenSSL::PKey::RSA.new (application_private_key)
        return JWT.encode token_payload, rsa_private, 'RS256'
    end
    

    Note: after you have generated a jwt, it is valid for 24 hours.

  6. Use the Voice API to create your Call:

    <?php
    
    include 'application_generate_jwt.php';
    
    //Connection information
    $base_url = 'https://api.nexmo.com' ;
    $version = '/v1';
    $action = '/calls';
    
    //User and application information
    $application_id = "id-for-your-voice-application";
    
    //Mint your JWT
    $keyfile="application_secret_key.txt";
    
    $jwt = generate_jwt($application_id, $keyfile);
    
    //Add the JWT to the request headers
    $headers =  array('Content-Type: application/json', "Authorization: Bearer " . $jwt ) ;
    
    //Change the to parameter to the number you want to call
    $payload = '{
        "to":[{
            "type": "phone",
            "number": "441632960961"
        }],
        "from": {
            "type": "phone",
            "number": "441632960960"
        },
        "answer_url": ["https://nexmo-community.github.io/ncco-examples/first_call_talk.json"]
    }';
    
    //Create the request
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $base_url . $version . $action);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    
    $response = curl_exec($ch);
    
    echo $response;
    
    import json
    import requests
    from datetime import datetime
    import calendar
    from jose import jwt
    import requests
    from application_generate_jwt import generate_jwt
    import ConfigParser
    from <my jwt helper functions> import generate_jwt
    
    #Set the endpoint
    base_url = "https://api.nexmo.com"
    version = "/v1"
    action = "/calls"
    
    #Application and call information
    application_id = "id-for-your-voice-application"
    keyfile = "application_secret_key.txt"
    #Create your JWT
    jwt = generate_jwt(application_id, keyfile)
    
    #Create the headers using the jwt
    headers = {
        "Content-type": "application/json",
        "Authorization": "Bearer {0}".format(jwt)
    }
    
    #Change the to parameter to the number you want to call
    payload = {
        "to":[{
            "type": "phone",
            "number": "441632960961"
        }],
        "from": {
            "type": "phone",
            "number": "441632960960"
        },
        "answer_url": ["https://nexmo-community.github.io/ncco-examples/first_call_talk.json"]
    }
    
    response = requests.post( base_url + version + action , data=json.dumps(payload), headers=headers)
    
    if (response.status_code == 201):
        print response.content
    else:
        print( "Error: " + str(response.status_code) + " " +    response.content)
    
    require "net/http"
    require "uri"
    require "json"
    #import the file where you have declared generate_jwt
    require_relative 'application_jwt_generate'
    
    #Set the endpoint
    base_url = 'https://api.nexmo.com'
    version = "/v1"
    action = "/calls"
    
    #Application and call information
    application_id = "id-for-your-voice-application"
    
    #Add a jwt to the request header
    keyfile="application_secret_key.txt"
    jwt = generate_jwt(application_id, keyfile)
    headers = {
            "Content-type" => "application/json",
            "Authorization" => "Bearer %s" % jwt
    }
    
    #Change the to parameter to the number you want to call
    payload = {
        "to" => [{
            "type"=> "phone",
            "number"=> "441632960961"
        }],
        "from"=> {
            "type"=> "phone",
            "number"=> "441632960960"
        },
        "answer_url"=> ["https://nexmo-community.github.io/ncco-examples/first_call_talk.json"]
    }.to_json
    
    uri = URI(base_url + version + action )
    req = Net::HTTP::Post.new(uri.path, headers)
    req.body = payload
    
    response = Net::HTTP.start(uri.host, uri.port,
            :use_ssl => uri.scheme == 'https') {|http| http.request req}
    
    case response
    when Net::HTTPSuccess
      puts "success" +  response.body
    else
      puts "Error" + response.body
    end