Twilio sends call completed status for all calls - node.js

I was setting up Status Callbacks for calls in nodeJS. I've set the 'CALL STATUS CHANGES' in phone number configuration to do a POST request to my node. But twilio seems to be sending call status completed for all calls no matter is the answer was 'busy', 'unanswered' or 'completed'.
Here's my node code for displaying call status sent by Twilio:
app.post("/status", (req, res) => {
res.sendStatus(200);
console.log(req.body);
});
Output for a busy call:
{
ApiVersion: '2010-04-01',
Called: '',
CallStatus: 'completed',
Duration: '15',
From: 'client:AbrasiveHannahLiberty',
CallDuration: '15',
Direction: 'inbound',
Timestamp: 'Fri, 20 Mar 2020 05:43:09 +0000',
AccountSid: 'ACxxxxxxxx',
CallbackSource: 'call-progress-events',
ApplicationSid: 'APxxxxxxxx',
Caller: 'client:AbrasiveHannahLiberty',
SequenceNumber: '0',
To: '',
CallSid: 'CAxxxxxxxx'
}

Twilio client calls have two call legs. When the Twilio client places an outbound call, that is the parent leg. Twilio automatically answers that call leg, so the TwiML associated with your Voice Request URL can be accessed, all calls will result in completed calls.
Once the client call is placed, the TwiML Application you defined (and convey to your Twilio Client via the Access Token) has a Request URL which is used to obtain TwiML, so Twilio knows what to do with the call and for example, places an outbound call to the public switched telephone network (PSTN), the child leg.
It is the TwiML in that Request URL that should use a Dial verb with Number noun and the statusCallback URL to get the status you are looking for.
TwiML™ Voice: Number

I think you should check the statusCallback URL in $dial->number() have to be different from URL of statusCallback you have configured in TwiML App?
Example: if statusCallback URL you config in TwiML App is ..../status, then in the $dial->number() you have to set to ..../events (or anything URL you like) just like this:
$response = new VoiceResponse();
$dial = $response->dial('');
$dial->number('+12349013030',
['statusCallbackEvent' => 'initiated ringing answered completed',
'statusCallback' => '..../events',
'statusCallbackMethod' => 'POST']);

Related

How do I automatically cancel Twilio outbound call as soon as the call status changes to ringing

hello there I am using Twilio to place outbound call I have local node app that initiates a call. I also have Twilio status callback function which monitors the status of call. I want to cancel/end the call when the call status changes to "ringing" I have tried to end a call with hangup and reject TwiML. I was expecting a call to be cancelled automatically but my phone keeps ringing.
node code for placing a call
router.post('/call', async (requ, resp) => {
const accountSid = myaccoundsid;
const authToken = myauthtoken;
const client = require('twilio')(accountSid, authToken);
client.calls
.create({
to: 'to_number',
from: 'from_number',
url: 'http://demo.twilio.com/docs/voice.xml',
statusCallback: 'url_to_my_status_call_back_function',
statusCallbackMethod: 'POST',
statusCallbackEvent: ['initiated', 'ringing', 'answered', 'completed'],
})
.then((call) => {
console.log(call.status);
});
});
The call status returned in my terminal is queued.
My status callback function
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const response = new VoiceResponse();
exports.handler = function(context, event, callback) {
if(event.CallStatus ==='ringing'){
console.log("Call status changed: "+ event.CallStatus);
response.hangup();
console.log(response.toString());
}
callback(null, response);
};
Twilio console
any help will be appreciated thank you
Twilio developer evangelist here.
First, you should likely check with the Twilio terms, specifically the terms related to voice calls to ensure that you do not fall foul of this rule:
Customer will not have, in a given month, (a) a high volume of unanswered outbound voice call attempts from a single originating phone number or (b) a low average outbound voice call duration. Telecommunications providers may independently block Customer’s use of their networks if Customer engages in any of the foregoing behavior.
If you have a legitimate reason to hang up a call as soon as it starts ringing, then read on.
Status callback webhooks are asynchronous, they happen outside of the call itself, so returning TwiML to them does not affect the call. Instead, to use a status callback to affect a call, you need to use the REST API to modify the call and complete it.
An example of this might be:
exports.handler = async function(context, event, callback) {
if(event.CallStatus ==='ringing'){
const client = context.getTwilioClient();
await client.calls(event.CallSid).update({ status: 'canceled' });
}
callback(null, response);
};

How to get Conference Sid at the time of dialing twilio call

I've been working with twilio, using Node.js, and dialing call between two web end points. One is client and other is agent. I'm using following code to dial call.
function dialCall(calledNumber, url) {
client.calls.create({
to: `client:${calledNumber}`,
from: twilioNumber,
url: url
})
.then(call => call.sid));
}
I'm using following twiml to establish a call.
const generateTwiml = (conferenceName) => {
let twimlResponse = new VoiceResponse();
twimlResponse.say(`Welcome to unity dialer.`, {
voice: 'alice',
});
const dial = twimlResponse.dial({
timeLimit: '600',
});
dial.conference({
startConferenceOnEnter: true,
endConferenceOnExit: true
}, "Test Room");
return twimlResponse.toString();
};
I've been successfully calling both agents and clients and getting callSid of both calls. However, my question is that at this point of time I also want to get conference Sid as well as I'm dialing the call as conference. What is the method to get that. As per documentation there is a method to fetch conference using conference name and status. However, if I use this some time the same is not returned due to race condition and I have to implement set time out function for same arbitrary delay. I've been getting the result but is there any other solution available for that.
Twilio developer evangelist here.
At the time you return the TwiML to create the conference there is not yet a conference resource so there's no way to get the conference SID at that stage.
As you describe, you can use the conference resource to list conferences and filter by the name you give it. However, you can't list the conferences at the time you return the TwiML because that conference hasn't been created by then.
Rather than setting a timeout, which could be flaky, I recommend you use the statusCallback attribute of the <Conference> TwiML to set a URL to callback to when the conference starts. In the parameters to that callback you will get the ConferenceSid.

Getting 11205 error when trying to use calls().update to transfer to conference room

I'm trying to move the inbound caller to a conference room:
//this is the endpoint for the voice webhook
app.post('/voice',(req,res)=>{
sid=req.body.CallSid;
conferenceName="test conference room";
params={'conferenceName':conferenceName};
url='https://appname.herokuapp.com/addToConference?conferenceName=test';
console.log('now updating inbound call sid '+sid);
client.calls(sid).update({
url:url,
method:'GET'
});
});
//this is the endpoint for adding a caller to a conference
app.get('/addToConference',(req,res)=>{
var conferenceName=req.query.conferenceName;
const response=new VoiceResponse();
response.say("Now connecting you to conference "+conferenceName);
dial=response.dial();
dial.conference(conferenceName);
responseTwiml=response.toString();
console.log("responseTwiml: "+responseTwiml);
res.send(responseTwiml);
});
Console logging shows that the .update() call is reached:
now updating inbound call sid 9j92f892309
But then the Twilio debugger shows an 11205 error, where the url is https://appname.herokuapp.com/voice/. The console log does not show Now connecting you to conference test, so I'm guessing the /addToConference endpoint isn't being reached. Heroku error log shows a Request timeout error.
How can I reach the endpoint and drop the inbound caller into a conference call? If it matters, I want the app to then call someone else, interact with them, and then transfer that call recipient to the conference where the inbound caller is waiting.
Twilio developer evangelist here.
There's a couple of issues here. To start with, the 11205 error is because of a timeout from the call to your /voice endpoint. The issue here is that you never return anything from the function with the res. You could fix that one by calling res.send('') at the end of that function.
However...
There is no need to do the redirect that you're doing at all. You can use the initial webhook response to both drop the caller into the conference and dial the other party. You would do this with the following code:
app.post('/voice',(req,res)=>{
conferenceName="test conference room";
url='https://appname.herokuapp.com/addToConference?conferenceName=' + conferenceName;
client.calls.create({
from: YOUR_TWILIO_NUMBER,
to: THE_OTHER_PERSON,
url: url
});
const response=new VoiceResponse();
response.say("Now connecting you to conference "+conferenceName);
dial=response.dial();
dial.conference(conferenceName);
responseTwiml=response.toString();
res.set('Content-Type': 'text/xml');
res.send(responseTwiml);
});
For this you will still need the /addToConference endpoint which just adds the other caller to the conference, which would look like this:
app.get('/addToConference',(req,res)=>{
var conferenceName=req.query.conferenceName;
const response=new VoiceResponse();
response.say("Now connecting you to conference "+conferenceName);
dial=response.dial();
dial.conference(conferenceName);
responseTwiml=response.toString();
console.log("responseTwiml: "+responseTwiml);
res.send(responseTwiml);
});
You could also redirect the caller in the way you wanted to do in the first place, but instead of modifying the call with the REST API you would use the TwiML <Redirect> verb. That would look like this:
app.post('/voice',(req,res)=>{
conferenceName="test conference room";
url='https://appname.herokuapp.com/addToConference?conferenceName=' + conferenceName;
client.calls.create({
from: YOUR_TWILIO_NUMBER,
to: THE_OTHER_PERSON,
url: url
});
const response=new VoiceResponse();
response.redirect(url);
responseTwiml=response.toString();
res.set('Content-Type': 'text/xml');
res.send(responseTwiml);
});
Let me know if that helps at all.

Prompt agent for acceptance before dequeuing an incoming call

I've set up a basic workflow on Twilio/node.js, that will transfer an incoming call to agents' phones. Because the agents might have a voicemail setup on their phone/cell phone, I wanted to prompt them for acceptance through a "gather" instruction. If they press 1, then the call will be transferred ("dequeue" instruction). Otherwise, the reservation is to be rejected.
My problem is that I don't know how to order the instructions: if I gather before enqueuing, then gather doesn't seem to know yet "who to ask". But if enqueue before gathering, then the prompt message will be heard by the caller (incoming client) and it will be too late to reject the reservation...
Twilio developer evangelist here.
In this case you should not respond to the assignment callback with "dequeue". Instead, you should use the "call" instruction to initiate a call to the agent instead.
With the call instruction you include a URL that will be requested when the call connects to the agent. At this point you should read out your message and use <Gather> to ensure the agent is ready for the call. When the agent presses a button or the <Gather> times out, the action attribute of the <Gather> will be requested. Then, if you find the user did enter a digit, by checking the Digits parameter you can respond with TwiML to <Dial> the <Queue>. In order to dial the actual caller that the reservation refers to, you need to add the reservationSid attribute to the <Queue> element. You will receive the ReservationSid as a parameter in the task assignment webhook. When the call bridges it will accept the reservation.
If the <Gather> times out, then you should just reject the reservation and it will be passed to the next worker.
Let me know if that helps at all.
Edit
Example TwiML for this situation.
You first respond to the assignment callback with JSON, using a call instruction:
{
"instruction": "call",
"to": AGENT_PHONE_NUMBER,
"from": YOUR_TWILIO_NUMBER,
"url": "https://example.com/worker_call?ReservationSid=${req.body.ReservationSid}&TaskSid=${req.body.TaskSid}"
}
Note, I have added the ReservationSid and TaskSid from the incoming request to the URL of the webhook that is called when the worker answers their phone.
Then when the worker answers their phone Twilio calls to the url we set above and we use it to ask them if they want to take the call.
# /worker_call
<Response>
<Gather action="/gather_result?ReservationSid=${req.query.ReservationSid}&TaskSid=${req.query.TaskSid}" numDigits="1">
<Say voice="alice">You have an incoming call, dial any number to accept</Say>
</Gather>
<Redirect>/gather_result?ReservationSid=${req.query.ReservationSid}&TaskSid=${req.query.TaskSid}</Redirect>
</Response>
Note, I am passing the ReservationSid and TaskSid along from the incoming request URL to the URL of the <Gather> action. Now, when the action URL is called we need to respond based on whether the call was accepted or not. I need to use some actual code for this rather than just TwiML, so I'll do it with Node. We need to be able to call the reservations resource in the REST API, so this comes with some setup:
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const workspaceSid = 'WSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const twilio = require('twilio')
const client = twilio(accountSid, authToken);
app.post('/gather_result', function(req, res) {
const twiml = new twilio.twiml.VoiceResponse();
if (req.body.Digits && req.body.Digits.length > 0) {
// There was a positive result, call the Queue and connect the caller
const dial = twiml.dial();
dial.queue({queue: req.query.ReservationSid});
res.send(twiml.toString());
} else {
// no response, reject the reservation.
client.taskrouter.v1
.workspaces(workspaceSid)
.tasks(req.query.TaskSid)
.reservations(req.query.ReservationSid)
.update({ reservationStatus: 'rejected' })
.then(function() {
// once the reservation is updated, then hangup the call.
twiml.hangup();
res.send(twiml.toString());
});
}
});

Attach messagingServiceSid to a twiml response in nodejs library

I have a webhook setup, and I am able to receive messages and reply to them. I would like to have the responses sent by my webhook to have messagingServiceSid attached to them.
I didn't find on documentation a way to configure that for responses from my webhook, only for new SMS using
client.sendMessage({
messagingServiceSid: 'MG9752274e9e519418a7406176694466fa',
to: '+16518675309',
body: 'Phantom Menace was clearly the best of the prequel trilogy.'
}, function(err, message) {
console.log(message);
});
Is there something similar for this code? Is it doable through the UI?
app.post('/foo/bar/sms', twilio.webhook({
host:'gassy-ocelot-129.herokuapp.com',
protocol:'https'
}), function(request, response) {
var twiml = new twilio.TwimlResponse();
twiml.message('This HTTP request came from Twilio!');
response.send(twiml);
});
Images:
No messagingService on reply messages sent using twiml response
Message Detail view from logs
Twilio developer evangelist here.
As far as I'm aware, there's no way to reply to message from a message service with TwiML.
However, rather than using TwiML, you could just send the SMS back to your user from the REST API and return an empty <Response> to the incoming webhook. Something a bit like this:
app.post('/foo/bar/sms', twilio.webhook({
host:'gassy-ocelot-129.herokuapp.com',
protocol:'https'
}), function(request, response) {
// send the message from the message service
client.sendMessage({
messagingServiceSid: 'MG9752274e9e519418a7406176694466fa',
to: request.body.From,
body: 'Your message'
}, function(err, message) {
console.log(message);
});
// send empty TwiML response
var twiml = new twilio.TwimlResponse();
response.send(twiml);
})
Let me know if that helps at all.
If you receive an incoming SMS on a phone number currently set up to that Messaging Service (via the web ui or phone number REST), then the incoming requests will have MessagingServiceSid in the query string.

Resources