I am using Twilio Flex to support a call center. I have a TaskRouter workflow set up where Task Reservation Timeout is set to 120 seconds. In its filter, I've created two routing steps. The first one finds matching workers in the main queue and has a timeout of 120 seconds. After 120 seconds, it should move to Call Forward Queue. In the call forward queue, no workers exist (target worker expression: 1==2). I'm catching all these events with a "trEventListener" function. Once a task is moved into the Call Forward queue, I call the "callForward" function which uses twiml.dial() to connect the call to an external number. I also change this task's status to "canceled" with a custom reason so I can track it in flex insights. I am using the guide in this link to form my logic: https://support.twilio.com/hc/en-us/articles/360021082934-Implementing-Voicemail-with-Twilio-Flex-TaskRouter-and-WFO.
Call forwarding is working fine but according to Flex insights, there are some calls that get handled after 120 seconds (between 120 - 300 seconds). Ideally, these should be forwarded as well. There is also no error logged for me to track down why this is happening to only a handful of calls.
Furthermore, in some cases, when I try to change the task status to cancel with my custom reason, it spits out the following error: Cannot cancel task because it is not pending or reserved. In other cases, it works fine. It's again hard to figure out why it's selectively working and not consistent in its behavior.
Here is the function code.
trEventListener.js:
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient();
let task = '';
let workspace = '';
console.log(`__[trEventStream]__: Event recieved of type: ${event.EventType}`);
// setup an empty success response
let response = new Twilio.Response();
response.setStatusCode(204);
// switch on the event type
switch(event.EventType) {
case 'task-queue.entered':
// ignore events that are not entering the 'Call Forward' TaskQueue
if (event.TaskQueueName !== 'Call Forward') {
console.log(`__[trEventStream]__: Entered ${event.TaskQueueName} queue - no forwarding required!`);
return callback(null, response);
}
console.log(`__[trEventStream]__: entered ${event.TaskQueueName} queue - forwarding call!`);
task = event.TaskSid;
workspace = event.WorkspaceSid;
const ta = JSON.parse(event.TaskAttributes);
const callSid = ta.call_sid;
let url = `https://${context.DOMAIN_NAME}/forwardCall`;
// redirect call to forwardCall function
client.calls(callSid).update({
method: 'POST',
url: encodeURI(url),
}).then(() => {
console.log(`__[trEventStream]__: [SUCCESS] ~> Task with id ${task} forwarded to external DID`);
// change task status to canceled so it doesn't appear in flex or show up as a pending task
client.taskrouter.workspaces(workspace)
.tasks(task)
.update({
assignmentStatus: 'canceled',
reason: 'Call forwarded'
})
.then(task => {
console.log(`__[trEventStream]__: [SUCCESS] ~> Task canceled`);
return callback(null, response);
}).catch(err => {
console.log(`__[trEventStream]__: [ERROR] ~> Task not marked complete: `, err);
// doesn't warrant reponse 500 since call still forwarded :)
return callback(null, response);
});
}).catch(err => {
console.log(`__[trEventStream]__: [ERROR] ~> Task failed to forward to external DID: `, err);
response.setStatusCode(500);
return callback(err, response);
});
break;
default:
return callback(null, response);
}
};
callForward.js:
exports.handler = function(context, event, callback) {
console.log(`forwarding call`);
// set-up the variables that this Function will use to forward a phone call using TwiML
// REQUIRED - you must set this
let phoneNumber = event.PhoneNumber || context.NUMBER;
// OPTIONAL
let callerId = event.CallerId || null;
// OPTIONAL
let timeout = event.Timeout || null;
// OPTIONAL
let allowedCallers = event.allowedCallers || [];
let allowedThrough = true;
if (allowedCallers.length > 0) {
if (allowedCallers.indexOf(event.From) === -1) {
allowedThrough = false;
}
}
// generate the TwiML to tell Twilio how to forward this call
let twiml = new Twilio.twiml.VoiceResponse();
let dialParams = {};
if (callerId) {
dialParams.callerId = callerId;
}
if (timeout) {
dialParams.timeout = timeout;
}
if (allowedThrough) {
twiml.dial(dialParams, phoneNumber); // making call :)
}
else {
twiml.say('Sorry, you are calling from a restricted number. Good bye.');
}
// return the TwiML
callback(null, twiml);
};
Any kind of help and/or guidance will be appreciated.
Twilio developer evangelist here.
When you redirect a call from a task, its task is cancelled with the reason "redirected" so you don't need to cancel it yourself.
Your code was failing to update the task occasionally because of a race condition between your code and the task getting cancelled by Twilio.