Skip to main content

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.

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


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


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


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


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


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);

}
}
}
}

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


Reply