Skip to main content
The First Step
August 26, 2021
Question

Pause & Resume API Call

I am using the test harness call here https://developers.ringcentral.com/api-reference/Call-Control/pauseResumeCallRecording

The attributes are being populated from the webhook subscription response when on a call "/restapi/v1.0/account/~/telephony/sessions" and believe the values being used are correct.

accountId*

Internal identifier of a RingCentral account or tilde (~) to indicate the account logged-in within the current session

telephonySessionId*

Internal identifier of a call session

partyId*

Internal identifier of a call party

recordingId*

Internal identifier of a recording

Using the CURL example the data being sent

--data '{"active":true}'


The response being received for this request is "400 Bad Request" with message "Invalid Request"

I have logged a support ticket for this but wait a response so if anybody could shine some light on this would be appreciated.

    7 replies

    PhongVu
    Community Manager
    Community Manager
    August 26, 2021

    Did you authenticate your app with the user's credentials of the user who is a party of the call?

    The First Step
    August 26, 2021

    Ye it was myself on the call and authenticated via the website ui to get authorisation token.

    PhongVu
    Community Manager
    Community Manager
    August 31, 2021

    Here you go with a quick sample app.

    1. Provide your app credentials and user login credentials

    2. Put the caller phone number into the 'customerPhoneNumbers' array. This is just a fake condition to decide a recording.

    3. Make a subfolder 'recordings'

    4. Make a call from that 'customer' number to one of the numbers belong to the authenticated user extension (the one logs in the app)

    The demo app will start recording (after 10 secs), pause after 1 min and resume after 10 secs. Then it will download the recording after 30 secs when the call is disconnected.

    You need to install the RingCentral JS SDK before running the script.

    const RingCentral = require('@ringcentral/sdk').SDK
    const Subscriptions = require('@ringcentral/subscriptions').Subscriptions;
    var fs = require('fs')
    var async = require("async");
    
    RINGCENTRAL_CLIENTID = "";
    RINGCENTRAL_CLIENTSECRET = "";
    RINGCENTRAL_SERVER = 'https://platform.devtest.ringcentral.com'
    
    RINGCENTRAL_USERNAME = "";
    RINGCENTRAL_PASSWORD = ""
    RINGCENTRAL_EXTENSION = ""
    
    const rcsdk = new RingCentral({
      server: RINGCENTRAL_SERVER,
      clientId: RINGCENTRAL_CLIENTID,
      clientSecret: RINGCENTRAL_CLIENTSECRET
    })
    
    var platform = rcsdk.platform()
    const subscriptions = new Subscriptions({
       sdk: rcsdk
    });
    var subscription = subscriptions.createSubscription();
    
    platform.login({
            username: RINGCENTRAL_USERNAME,
            extension: RINGCENTRAL_EXTENSION,
            password: RINGCENTRAL_PASSWORD
          })
    
    platform.on(platform.events.loginSuccess, async function(e){
        console.log("Login success")
        subscribeForNotification
    });
    
    function subscribeForNotification(){
      var eventFilter = ['/restapi/v1.0/account/~/extension/~/telephony/sessions']
      subscription.setEventFilters(eventFilter)
       .register()
       .then(async function(resp){
         console.log('Ready for getting account NSN events')
       })
       .catch(function(e){
         throw e
       })
    }
    
    var customerPhoneNumbers = ['+1650xxxxxxx']
    
    var callInfo = {
      sessionId: '', // use this to identify the call when handle multiple calls
      partyId: '', // use this to create the recording endpoint
      telSessionId: '', // use this to create the recording endpoint
      recordingId: ''
    }
    
    
    subscription.on(subscription.events.notification, async function(msg) {
        var body = msg.body
        var party = msg.body.parties[
        if (party.hasOwnProperty("extensionId")){
          console.log(JSON.stringify(msg.body));
          if (party.direction == "Inbound" && party.status.code == "Answered"){
            if (!party.hasOwnProperty('recordings')){
              var canRecord = customerPhoneNumbers.find(customer => customer == party.from.phoneNumber)
              if (canRecord) {
                console.log("Callee Answered => It's time to call recording.")
                callInfo.sessionId = body.sessionId
                callInfo.partyId = party.id
                callInfo.telSessionId = body.telephonySessionId
                // start recording immediately or make a delay
                setTimeout(function(){
                  startRecording()
                }, 10000)
              }
            }
          }else if (party.status.code == "Disconnected"){
            // Want to download the call recording after the call ended?
            if (body.sessionId == callInfo.sessionId && party.hasOwnProperty("recordings")){
              console.log("Call has recording => Download it in 30 seconds")
              setTimeout(function(sessionId){
                readExtensionCallLogs(sessionId)
              }, 30000, callInfo.sessionId)
            }
            callInfo.sessionId = ""
            callInfo.partyId = ""
            callInfo.telSessionId = ""
            console.log("Call Disconnected => Reset getCallSessionInfo")
          }
        }else{
          console.log("caller of inbound call")
        }
    });
    
    
    async function startRecording(){
      console.log("startRecording")
      if (callInfo.telSessionId == '' || callInfo.partyId == '')
        return
      var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings`
      try {
        var resp = await platform.post(endpoint)
        var jsonObj = await resp.json()
        callInfo.recordingId = jsonObj.id
        console.log(jsonObj)
        // stop recording after 1 min
        setTimeout(function(){
          pauseRecording()
        }, 60000)
      }catch(e){
        console.log(e)
      }
    }
    
    async function pauseRecording(){
      console.log("pauseRecording")
      if (callInfo.telSessionId == '' || callInfo.partyId == '')
        return
      var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings/${callInfo.recordingId}`
      var params = {
        active: false
      }
      try {
        var resp = await platform.patch(endpoint, params)
        var jsonObj = await resp.json()
        console.log(jsonObj)
        // resume recording after 10 secs
        setTimeout(function(){
          resumeRecording()
        }, 10000)
      }catch(e){
        console.log(e)
      }
    }
    
    async function resumeRecording(){
      console.log("resumeRecording")
      if (callInfo.telSessionId == '' || callInfo.partyId == '')
        return
      var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings/${callInfo.recordingId}`
      var params = {
        active: true
      }
      try {
        var resp = await platform.patch(endpoint, params)
        var jsonObj = await resp.json()
        console.log(jsonObj)
      }catch(e){
        console.log(e)
      }
    }
    
    async function readExtensionCallLogs(sessionId){
      var endpoint = `/restapi/v1.0/account/~/extension/~/call-log`
      console.log(endpoint)
      var params = {
          sessionId: sessionId
      }
      try {
        var resp = await platform.get(endpoint, params)
        var jsonObj = await resp.json()
        console.log(jsonObj)
        async.each(jsonObj.records,
            function(record, callback){
              console.log("THIS CALL HAS A RECORDING: " + record.recording.contentUri)
              saveAudioFile(record)
            },
            function(err){
              console.log("This call does not have a recording.")
            }
        );
      }catch(e){
        var err = e.toString();
        console.log("READ FAILED: " + err)
      }
    }
    
    
    async function saveAudioFile(record){
      var resp = await platform.get(record.recording.contentUri)
      var buffer = await resp.buffer()
      var destFile = './recordings/' + record.recording.id + '.mp3'
      fs.writeFileSync(destFile, buffer);
      console.log("CALL RECORDING SAVED AT: " + destFile)
    }
    

    Let me know if you have further questions

    The First Step
    August 31, 2021

    Is it possible to have a .net equivalent for the above?

    PhongVu
    Community Manager
    Community Manager
    August 31, 2021

    I can implement it later. Just too busy right now.

    PhongVu
    Community Manager
    Community Manager
    September 1, 2021

    Here you go. A quick and dirty .Net sample code. You need to install the latest RingCentral .Net SDK

    using System;
    using System.IO;
    using Newtonsoft.Json.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using RingCentral;
    using RingCentral.Net.Events;
    using Newtonsoft.Json;
    using RingCentral.Net.PubNub;
    using System.Net;
    using System.Collections.Specialized;
    using System.Linq;
    
    namespace Quick_Sample
    {
        class Program
        {
            const string RINGCENTRAL_CLIENTID = "";
            const string RINGCENTRAL_CLIENTSECRET = "";
            const bool   RINGCENTRAL_PRODUCTION = false;
    
            const string RINGCENTRAL_USERNAME = "";
            const string RINGCENTRAL_PASSWORD = "";
            const string RINGCENTRAL_EXTENSION = "";
            
            static RestClient rcsdk;
    
            static string[] customerPhoneNumbers = { "+1650xxxxxxx" };
    
            class CallInfo {
                public String sessionId = ""; // use this to identify the call when handle multiple calls
                public String partyId = ""; // use this to create the recording endpoint
                public String telSessionId = ""; // use this to create the recording endpoint
                public String recordingId = "";
            }
            static CallInfo callInfo;
    
            static void Main(string[] args)
            {
                rcsdk = new RestClient(RINGCENTRAL_CLIENTID, RINGCENTRAL_CLIENTSECRET, RINGCENTRAL_PRODUCTION);
                rcsdk.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD).Wait();
                var eventsExtension = new EventsExtension();
                rcsdk.InstallExtension(eventsExtension).Wait();
    
                eventsExtension.RequestSuccess += EventHandler;
                callInfo = new CallInfo();
                pubnub_notification_record_call().Wait();
            }
            static void EventHandler(object sender, HttpMessages httpMessages)
            {
                var rateLimitRemaining = httpMessages.httpResponseMessage.Headers.First(i => i.Key == "X-Rate-Limit-Remaining").Value.First();
                Console.WriteLine("Remaining: " + rateLimitRemaining);
            }
            static private async Task pubnub_notification_record_call()
            {
                var pubNubExtension = new PubNubExtension();
                await rcsdk.InstallExtension(pubNubExtension);
                try
                {
                    var eventFilters = new[]
                    {
                        "/restapi/v1.0/account/~/extension/~/telephony/sessions"
                    };
                    var subscription = await pubNubExtension.Subscribe(eventFilters, message =>
                    {
                        dynamic jsonObj = JObject.Parse(message);
                        var body = jsonObj.body;
                        var party = jsonObj.body.parties[0];
                        if (party.extensionId != null)
                        {
                            if (party.direction == "Inbound" && party.status.code == "Answered")
                            {
                                if (party.recordings == null)
                                {
                                    var canRecord = customerPhoneNumbers.Where(x => x.ToString() == party.from.phoneNumber);
                                    if (canRecord != null)
                                    {
                                        Console.WriteLine("Callee Answered => It's time to call recording.");
                                        callInfo.sessionId = body.sessionId;
                                        callInfo.partyId = party.id;
                                        callInfo.telSessionId = body.telephonySessionId;
                                        Console.WriteLine("Can start recording");
                                        // start recording immediately or make a delay
                                        startRecording().Wait();
                                    }
                                }
                            }
                            else if (party.status.code == "Disconnected")
                            {
                                // Want to download the call recording after the call ended?
                                if (body.sessionId == callInfo.sessionId && party.recordings != null)
                                {
                                    Console.WriteLine("Call has recording => Download it in 30 seconds");
                                    Thread.Sleep(30000);
                                    readExtensionCallLog(callInfo.sessionId).Wait();
                                    Console.WriteLine("Call Disconnected => Reset getCallSessionInfo");
                                }
                            }
                        }
                        else
                        {
                            Console.WriteLine("Remote party event");
                        }
                    });
    
                    Console.WriteLine(subscription.SubscriptionInfo.id);
                    Console.WriteLine("Subscribed");
                    while (true)
                    {
                        Console.WriteLine("looping ...");
                        Thread.Sleep(5000);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            static private async Task startRecording()
            {
                if (callInfo.telSessionId == "" || callInfo.partyId == "")
                {
                    return;
                }
                try
                {
                    var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings().Post();
                    dynamic jsonObj = JObject.Parse(resp);
                    callInfo.recordingId = jsonObj.id;
                    Console.WriteLine(jsonObj.id);
                    // stop recording after 1 min
                    Thread.Sleep(60000);
                    pauseRecording().Wait();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            static private async Task pauseRecording()
            {
                if (callInfo.telSessionId == "" || callInfo.partyId == "" || callInfo.recordingId == "")
                {
                    return;
                }
                try
                {
                    CallRecordingUpdate update = new CallRecordingUpdate();
                    update.active = false;
                    var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings(callInfo.recordingId).Patch(update);
                    callInfo.recordingId = resp.id;
                    Console.WriteLine(resp.id);
                    // resume recording after 10 secs
                    Thread.Sleep(10000);
                    resumeRecording().Wait();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            static private async Task resumeRecording()
            {
                if (callInfo.telSessionId == "" || callInfo.partyId == "" || callInfo.recordingId == "")
                {
                    return;
                }
                try
                {
                    CallRecordingUpdate update = new CallRecordingUpdate();
                    update.active = true;
                    var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings(callInfo.recordingId).Patch(update);
                    callInfo.recordingId = resp.id;
                    Console.WriteLine(resp.id);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            static private async Task readExtensionCallLog(String sessionId)
            {
                Console.WriteLine("readExtensionCallLog");
                var parameters = new ReadUserCallLogParameters();
                parameters.sessionId = sessionId;
                var path = "recordings/";
                System.IO.Directory.CreateDirectory(path);
                var resp = await rcsdk.Restapi().Account().Extension().CallLog().List(parameters);
                foreach (var record in resp.records)
                {
                    var destFile = path + record.recording.id + ".mp3";
                    var contentUrl = record.recording.contentUri + "?access_token=" + rcsdk.token.access_token;
                    WebClient webClient = new WebClient();
                    webClient.DownloadFile(contentUrl, destFile);
                    Console.WriteLine("CALL RECORDING SAVED AT: " + destFile);
                    
                }
            }
        }
    }
    
    The First Step
    September 15, 2021

    Hi Phong,

    I now have the credentials working in production and getting the following error: -

    {

    "error" : "invalid_grant",

    "errors" : [ {

    "errorCode" : "OAU-140",

    "message" : "Invalid resource owner credentials"

    } ],

    "error_description" : "Invalid resource owner credentials"

    }


    I believe this may be related to the permissions for the app as the user being used in the app owner


    If you could advise here it would be appreciated.


    Regards,

    Martin Dearne