Given client code that makes an outgoing call like this:
var accountSid = configuration["accountSid"];
var authToken = configuration["authToken"];
TwilioClient.Init(accountSid, authToken);
var call = CallResource.Create(
machineDetection: "DetectMessageEnd",
asyncAmd: "true",
asyncAmdStatusCallback: new Uri("[URL]/callback/index"),
asyncAmdStatusCallbackMethod: HttpMethod.Post,
twiml: new Twilio.Types.Twiml("<Response><Say>Ahoy there!</Say></Response>"),
from: new Twilio.Types.PhoneNumber(configuration["fromPhoneNumber"]),
to: new Twilio.Types.PhoneNumber(configuration["toPhoneNumber"])
);
aka, asyncAmd is enabled and callback URL is specified, with my webhook controller action that looks like this:
[ApiController]
[Route("callback")]
public class CallbackController : TwilioController
{
[HttpPost]
[Route("index")]
public IActionResult Index()
{
var response = new VoiceResponse();
if (Request.Form.TryGetValue("AnsweredBy", out var answeredBy))
{
if (answeredBy != "human")
{
response.Say("this is the voice message");
}
}
return Content(response.ToString(), "text/xml");
}
}
why is it there is no voicemail being left?
Note: I am including the Twiml I want to say in CallResource.Create b/c I don't want a callback to get the message contents in case of a human answering.
I only need the callback performed for the results of AMD detection, and then to leave a voice message.
Do I do that with response.Say?
Thanks!
Twilio developer evangelist here.
Twilio answering machine detection can happen in synchronous or asynchronous mode. From this blog post:
With Async AMD on Twilio, AMD is done asynchronously (hence the name). When the call is answered, a call url is executed immediately, so if a person answers the call rather than voicemail, they can begin interacting with your application without any silence. Then “asynchronously”, or “in parallel”, AMD processes the call audio and determines what answered the call. When AMD processing is complete, the result (the AnsweredBy parameter) is sent to a second URL, the asyncAmdStatusCallback.
One key difference between standard AMD and async AMD is how you modify the call once receiving the AMD result. With standard AMD, you have one URL and the result is sent to this URL just like any other outbound-api call. When your URL receives the result, you can check the AnsweredBy parameter and update the call accordingly with TwiML. With Async AMD, your call is already executing TwiML and you instead need to update the call via API.
In your case you are using async AMD, but you are not updating the call via the API.
You have two options. You can choose to use synchronous AMD and you can then respond to the result using TwiML like you are doing so far.
Alternatively, you can continue to use async AMD, but instead of responding to the webhook with TwiML, use the REST API to update the call with the new TwiML or with a new webhook URL.
One thing I would look out for too. Your initial TwiML is very short, your example code shows that it sends <Response><Say>Ahoy there!</Say></Response>. It is entirely possible that this TwiML will be completed before an answering machine is detected and since it is the only TwiML for the call, the call would then hang up. You may want to consider using a longer message or pausing so that you can get the result of the AMD.
Figured I'd follow up here. Thanks #philnash for the help. You were indeed right
I was returning Twiml from the asyncAMD webhook instead of updating the call.
my very short Twiml in the initiating call was not long enough
I got through the first part and things were still failing even with Twiml that was longer:
<Response><Say>Hello there. This is a longer message that will be about as long as the real message asking you to confirm or cancel your appointment. Hopefully it's long enough!</Say></Response>
BUT, that still was not long enough! By the time the asyncAMD callback was invoked, and I tried to use CallResource.Update, the call was already ended.
I ended up dumping about 2,000 characters of lorem ipsum into the outgoing call Twiml and the asyncAMD callback using CallResource.Update worked perfectly.
Related
To set this up, here is the use case:
IVR system calls someone and reads a long-ish twiml prompt at them (about 251 characters)
at the end of the prompt gather user input
if a non-human answers the phone, I'm using asyncAMD callback to get the result of AMD and then leave a voice message
I am currently placing an outbound call using this code:
var call = CallResource.Create(
machineDetection: "DetectMessageEnd",
asyncAmd: "true",
asyncAmdStatusCallback: new Uri("[URI to callback]"),
asyncAmdStatusCallbackMethod: HttpMethod.Post,
twiml: new Twilio.Types.Twiml("<Response><Say>[MyMessage]</Say></Response>"),
from: new Twilio.Types.PhoneNumber(configuration["fromPhoneNumber"]),
to: new Twilio.Types.PhoneNumber(configuration["toPhoneNumber"])
);
where "MyMessage" is about 251 characters long.
The answering machine bit works wonderfully, and I'm able to leave a voice message in case a non-human answers (see my question and subsequent answer for How to leave a voicemail using Twilio AMD? for details).
However, I cannot for the life of me figure out how to prolong the asyncAMD callback long enough for the initial prompt to be finished in the case a human answers.
I've tried adding all of these optional API tuning parameters, and I still can't get it to work:
machineDetectionTimeout: 59,
machineDetectionSpeechThreshold: 6000,
machineDetectionSpeechEndThreshold: 5000,
machineDetectionSilenceTimeout: 10000,
What are my options here? Bail on asyncAMD and use blocking AMD? I need to be able to leave a voice message in case of a non-human answering, but I need to push the results of the asyncAMD invoking it's callback long enough for the initial response to be read in a human answers.
I'm going to take this approach. In the recipient answers and says "hello" (which is likely they'll say it), answeredBy will return "human", but if they don't and just listen to the initial prompt, I get "unknown" back. At this point, I'm going to treat an answeredBy "unknown" as a person answering and listening to the initial Twiml prompt.
I am using the following libraries to connect a bot to a Google Pub/Sub endpoint to perform a simple reply to a card click event.
Google.Apis.HangoutsChat.v1 1.34.0.1233
Google.Cloud.PubSub.V1 1.0.0-beta18
When I construct my card, everything looks normal in the UI, including the button that is supposed to raise the event.
The topic and subscription contain the default settings, following the guide here
I found the following information from the Google documentation about retries here
Responding synchronously
A bot can respond to an event synchronously by returning a
JSON-formatted message payload in the HTTP response. The deadline for
a synchronous response is 30 seconds.
A synchronous response from a bot is always posted in the thread that
generated the event to the bot.
After clicking the button, my subscriber receives 3 duplicate events. The events have the correct response with all of the right metadata, but are exact duplicates of each other, including the id of the message itself.
I don't feel there is a necessarily large delay in the response of the bot (it should happen in <1 second for this test), so I am not sure why these messages are being duplicated.
I've also tried setting the thread id for the card when responding (via the Thread property itself, or the ThreadKey property), but I always seem to get a new thread id when I post a message.
var cardMessage = MessageSender.Spaces.Messages.Create(new Message()
{
Space = new Space()
{
Name = inReplyToThisMessage.Space.Name
},
Thread = new Thread()
{
Name = inReplyToThisMessage.Thread.Name
},
Cards = new List<Card>()
{
card
},
}, inReplyToThisMessage.Space.Name);
var sendCardResult = await cardMessage.ExecuteAsync().ConfigureAwait(false);
//Thread id of sendCardResult does not match inReplyToThisMessage.Thread.Name no matter what
Interestingly enough, trying to create a new message in response to the click event causes the bot to display a "Unable to connect to bot. Try again later", but displays 3 new messages. Also, when specifying an arbitrary thread key, this key is never echoed back in the bot's response.
Make sure you are returning the main event method properly. What looks to be happening is that you are making an asynchronous call to the chat, but then the chat is looking for a response from the actual event method itself. Google will traditionally try three times before giving up (even if it doesn't take thirty seconds)
If you are indeed returning the event call correctly after you made your api request, then there is something in your code that is causing the Google Bot to think it is not getting a response, so it tries three times. Since the issue could be multi-faceted I would need to look at how you are accepting and returning the click response.
This bug has finally been fixed by Google.
I'm using a simple Web API controller to accept a request from a client to process a payment. The Payflow payment request is started in a new task, and I immediately return a status to the client while I wait for the transaction to complete in my task.
However, in testing, if I send two requests back-to-back to my API, the second request will receive a null response from the SubmitTransaction method of the Payflownet API. Why is this happening?
Here is my method which is making the call
private NameValueCollection SubmitTransaction(NameValueCollection pfpParams)
{
string transactionString = GetTransactionString(pfpParams);
string pfpResponse = _pfNetApi.SubmitTransaction(transactionString, PayflowUtility.RequestId);
return HttpUtility.ParseQueryString(pfpResponse);
}
And here is the value of transactionString that I'm passing to the SubmitTransaction method in both cases (dummy credit card info):
ACCT=4111111111111111&EXPDATE=0115&COMMENT1=&COMMENT2=&CVV2=123&NAME=Joshua Dixon&STREET=123 x st&TENDER=C&ZIP=12345&AMT=5.00&TRXTYPE=S&USER=test&PWD=xxxx&PARTNER=Verisign&VENDOR=test
Whenever I send a single request with that string, the response is correct and expected. However, whenever I send two asynchronous requests with that string, if the first hasn't completed, the second response is always null.
OP's co-worker, answering this in case anyone else has this problem.
This seems to be an issue with thread-safety. Though the documentation indicates otherwise, PayflowNETAPI.SubmitTransaction does not seem to be thread-safe. The problem was solved by using a new instance of PayflowNETAPI for each transaction.
Scenario: Call is received and caller is placed in a conference using the following code
var response = new TwilioResponse();
response
.Say("Please wait while we attempt to locate the person you were trying to reach.")
.DialConference(string.Format("{0} Waiting Room", digits), new { beep = "false"})
return TwiML(response);
Now I need to Dial out and connect to a mobile users phone and prompt them to accept or send caller to voice mail. I'm assuming once the DialConference is initiate the original caller is placed on hold and no more TwiML is processed. So the only way to initiate a new call is to use the RestAPI.
However I would like to use the .net helper's fluent syntax and it "should" be possible. If you know an example of doing a simultaneous Dial using the fluent syntax, it should also work with the Conference verb.
How do I initiate an outbound call while having a received call put into a conference room, all using TwiML?
Twilio evangelist here.
There isn't a way using only TwiML to put Caller A into the conference, then dial Caller B. With your example above, Twilio will stop executing the TwiML once it hits the Dial and not start again until Caller A leaves the conference.
However, if you just want call screening, then you could check out this HowTo, which shows you how without the conference:
http://www.twilio.com/docs/howto/callscreening
Hope that helps.
Given an async controller:
public class MyController : AsyncController
{
[NoAsyncTimeout]
public void MyActionAsync() { ... }
public void MyActionCompleted() { ... }
}
Assume MyActionAsync kicks off a process that takes several minutes. If the user now goes to the MyAction action, the browser will wait with the connection open. If the user closes his browser, the connection is closed. Is it possible to detect when that happens on the server (preferably inside the controller)? If so, how? I've tried overriding OnException but that never fires in this scenario.
Note: I do appreciate the helpful answers below, but the key aspect of this question is that I'm using an AsyncController. This means that the HTTP requests are still open (they are long-lived like COMET or BOSH) which means it's a live socket connection. Why can't the server be notified when this live connection is terminated (i.e. "connection reset by peer", the TCP RST packet)?
I realise this question is old, but it turned up frequently in my search for the same answer.
The details below only apply to .Net 4.5
HttpContext.Response.ClientDisconnectedToken is what you want. That will give you a CancellationToken you can pass to your async/await calls.
public async Task<ActionResult> Index()
{
//The Connected Client 'manages' this token.
//HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, HttpContext.Response.ClientDisconnectedToken);
}
}
catch (TaskCanceledException e)
{
//The Client has gone
//you can handle this and the request will keep on being processed, but no one is there to see the resonse
}
return View();
}
You can test the snippet above by putting a breakpoint at the start of the function then closing your browser window.
And another snippet, not directly related to your question but useful all the same...
You can also put a hard limit on the amount of time an action can execute for by using the AsyncTimeout attribute. To use this use add an additional parameter of type CancellationToken. This token will allow ASP.Net to time-out the request if execution takes too long.
[AsyncTimeout(500)] //500ms
public async Task<ActionResult> Index(CancellationToken cancel)
{
//ASP.Net manages the cancel token.
//cancel.IsCancellationRequested will be set to true after 500ms
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, cancel);
}
}
catch (TaskCanceledException e)
{
//ASP.Net has killed the request
//Yellow Screen Of Death with System.TimeoutException
//the return View() below wont render
}
return View();
}
You can test this one by putting a breakpoint at the start of the function (thus making the request take more than 500ms when the breakpoint is hit) then letting it run out.
Does not Response.IsClientConnected work fairly well for this? I have just now tried out to in my case cancel large file uploads. By that I mean if a client abort their (in my case Ajax) requests I can see that in my Action. I am not saying it is 100% accurate but my small scale testing shows that the client browser aborts the request, and that the Action gets the correct response from IsClientConnected.
It's just as #Darin says. HTTP is a stateless protocol which means that there are no way (by using HTTP) to detect if the client is still there or not. HTTP 1.0 closes the socket after each request, while HTTP/1.1 can keep it open for a while (a keep alive timeout can be set as a header). That a HTTP/1.1 client closes the socket (or the server for that matter) doesn't mean that the client has gone away, just that the socket hasn't been used for a while.
There are something called COMET servers which are used to let client/server continue to "chat" over HTTP. Search for comet here at SO or on the net, there are several implementations available.
For obvious reasons the server cannot be notified that the client has closed his browser. Or that he went to the toilet :-) What you could do is have the client continuously poll the server with AJAX requests at regular interval (window.setInterval) and if the server detects that it is no longer polled it means the client is no longer there.