I'm setting up masked calling. When I get the following TwiML response after calling the masked number, it isn't dialing the number that I'm specifying. It's just saying the number instead after saying the content of the say
Here is the TwiML
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Say>Your call will be charged blah blah.</Say>
<Dial action="http://mywebsite.com/Call/CallComplete" callerId="+441XXXXX">
<Number>+44795XXXXX</Number>
</Dial>
</Response>
And here is the c#
public static string TwiMLDial(string maskedNumber, string to, string actionURL)
{
var response = new Twilio.TwiML.VoiceResponse();
response.Say("Your call will be charged blah blah.");
var dial = new Twilio.TwiML.Dial(action: actionURL, callerId: maskedNumber);
dial.Number(to);
response.Dial(dial);
return response.ToString();
}
I'm using c# .Net core. And have the following in my startup.cs which may be relevant:
services.AddMvc(config =>
{
// Add XML Content Negotiation
config.RespectBrowserAcceptHeader = true;
config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
})
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Twilio developer evangelist here.
The issue is that your endpoint is returning the response with the type text/plain and Twilio takes that to mean, just read this out.
You need to set your response Content-Type to text/xml or application/xml.
I'm not a C# developer I'm afraid, but hopefully that points you in the right direction.
Related
I'm trying connect Twilio autopilot to voice call with custom data. According to documentation there is Memory parameter where I can put custom data.
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect action="https://www.example.com/autopilot">
<Autopilot Memory={"CarModel":"Diablo","CarMake":"Lamborghini","CarYear":"2019"}>UAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</Autopilot>
</Connect>
</Response>
But when I try to put it via strong types I receive a message "Sorry an error occurred."
Here is the method:
public VoiceResponse ConnectAutopilot(Guid requestId)
{
var response = new VoiceResponse();
var autopilotUrl =
$"https://channels.autopilot.twilio.com/v1/{ AccountSid}/{AutopilotName }/twilio-voice";
var connect = new Connect
{
Action = new Uri(autopilotUrl)
};
var pilot = new Autopilot { Name = AutopilotName};
pilot.SetOption("Memory", new{requestId});
pilot.SetOption("TargetTask", "greeting");
connect.Append(pilot);
response.Append(connect);
response.Hangup();
return response;
}
I am currently using Forge Webhooks API to handle different events that might occur on a project. Everything works fine, except the payload signature check.
The reason why I want to check the payload is because the callback will end up on my API and I want to reject all requests that do not come from Forge's webhook service.
Steps I followed:
Add (register) secret key (token) on Forge. API Reference
Trigger an event that will eventually call my API for handling it.
Validating signature header. Followed this tutorial.
PROBLEM!!! My computedSignature is different from the signature received from Forge.
My C# code looks like this:
private const string SHA_HASH = "sha1hash";
var secretKeyBytes = Encoding.UTF8.GetBytes(ForgeAuthConfiguration.AntiForgeryToken);
using var hmac = new HMACSHA1(secretKeyBytes);
var computedHash = hmac.ComputeHash(request.Body.ReadAsBytes());
var computedSignature = $"{SHA_HASH}={computedHash.Aggregate("", (s, e) => s + $"{e:x2}", s => s)}";
For one example, Forge's request has this signature header: sha1hash=303c4e7d2a94ccfa559560dc2421cee8496d2d83
My C# code computes this signature: sha1hash=3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5
The same AntiForgeryToken was sent to Forge at step 1
Ok, I thought my C# code is broken, then I tried this online HMAC generator and for the given input, result is: 3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5 (same as C#)
Ok, maybe the online generator is broken, I tried their own code in node js and this is the result:
I have 3 ways of encrypting the SAME body using the SAME key and I get the SAME result every time. BUT those results are DIFFERENT from the signature provided by Forge, resulting in failing the check and rejecting a valid request...
Does anyone know what is happening with that signature?
Why is it different from my result if I follow their tutorial?
How are you validating your requests?
The code below is working at my side. Could you give it a try if it helps?
[HttpPost]
[Route("api/forge/callback/webhookbysig")]
public async Task<IActionResult> WebhookCallbackBySig()
{
try
{
var encoding = Encoding.UTF8;
byte[] rawBody = null;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
rawBody = encoding.GetBytes(reader.ReadToEnd());
}
var requestSignature = Request.Headers["x-adsk-signature"];
string myPrivateToken = Credentials.GetAppSetting("FORGE_WEBHOOK_PRIVATE_TOKEN");
var tokenBytes = encoding.GetBytes(myPrivateToken);
var hmacSha1 = new HMACSHA1(tokenBytes);
byte[] hashmessage = hmacSha1.ComputeHash(rawBody);
var calculatedSignature = "sha1hash=" + BitConverter.ToString(hashmessage).ToLower().Replace("-", "");
if (requestSignature.Equals(calculatedSignature))
{
System.Diagnostics.Debug.Write("Same!");
}
else
{
System.Diagnostics.Debug.Write("diff!");
}
}
catch(Exception ex)
{
}
// ALWAYS return ok (200)
return Ok();
}
If this does not help, please share with your webhook ID (better send email at forge.help#autodesk.com). We will ask engineer team to check it.
I'm having trouble getting Twilio to perform a Gather. The call initializes just fine but instead of waiting for a user keypress, the Gather just falls through to the next statement and hangs up.
My environment is Visual Studio 2015. .NET 4.6, MVC6, asp.net5. I have the RC1 Update installed. Nuget packages are Twilio version 4.4.1, Twilio.TwiML 3.3.6.
Here is a test WebAPI 2 controller:
[Route("api/[controller]")]
public class OutboundCallController : Controller
{
[HttpPost]
public IActionResult Post()
{
var twilioResponse = new TwilioResponse();
twilioResponse.BeginGather(new { timeout = "60", numDigits = "1", action = "Foo", method = "POST" });
twilioResponse.Say("Test Message Here");
twilioResponse.EndGather();
twilioResponse.Say("Fallthrough. Goodbye.");
return new ObjectResult(twilioResponse.ToString());
}
}
When Twilio receives the below data it Says "Test Message Here Fallthrough. Goodbye." all at once, without pausing, then promptly hangs up.
Using ngrok I can see that the reponse to the Twilio POST to my controller is:
<Response>
<Gather timeout="60" numDigits="1" action="Foo" method="POST">
<Say>Test Message Here</Say>
</Gather>
<Say>Fallthrough. Goodbye.</Say>
</Response>
Additionally, my Twilio log looks like (identical):
<Response>
<Gather timeout="60" numDigits="1" action="Foo" method="POST">
<Say>Test Message Here</Say>
</Gather>
<Say>Fallthrough. Goodbye.</Say>
</Response>
EDIT:
I've also tried changing the WebAPI to return a string instead of IActionResult. Nothing changes, same result.
[HttpPost]
public string Post()
{....}
ANSWER:
Turns out I wasn't returning the correct content type, I modified the return of the POST action, the full controller code is below:
[Route("api/[controller]")]
public class OutboundCallController : Controller
{
[HttpPost]
public IActionResult Post()
{
var twilioResponse = new TwilioResponse();
twilioResponse.BeginGather(new { timeout = "60", numDigits = "1", action = "Foo", method = "POST" });
twilioResponse.Say("Test Message Here");
twilioResponse.EndGather();
twilioResponse.Say("Fallthrough. Goodbye.");
return Content(twilioResponse.ToString(), "application/xml");
}
}
Twilio developer evangelist here.
I just copied your generated TwiML to twimlbin and was able to get it working with proper pause and without skipping.
Here's an exact copy of your generated TwiML.
Obviously when you press a number, it fails because the action is only set to be foo. If you set that action to something else like the example below, you will see that upon pressing a number, you should also get a message that says "Hi there"
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather timeout="60" numDigits="1" action="http://twimlets.com/echo?Twiml=%3CResponse%3E%3CSay%3EHi+there.%3C%2FSay%3E%3C%2FResponse%3E" method="POST">
<Say>Test Message Here</Say>
</Gather>
<Say>Fallthrough. Goodbye.</Say>
</Response>
Also, your C# code looks right but let me know you want to make your endpoint public so I can test it. It will be worth checking that what you're returning to Twilio is really XML (i.e. that it has <?xml version="1.0" encoding="UTF-8"?> on top) and that its content type is really XML.
Happy to help with any other questions.
I like a lot how the HttpClient is architectured - but I can't figure out how to add a "not quite standard" media type to be handled by the XmlSerializer.
This code:
var cli = new HttpClient();
cli
.GetAsync("http://stackoverflow.com/feeds/tag?tagnames=delphi&sort=newest")
.ContinueWith(task =>
{
task.Result.Content.ReadAsAsync<Feed>();
});
works fine when pointed to atom feeds that have Content-Type of "text/xml", but the one in the example fails with the "No 'MediaTypeFormatter' is available to read an object of type 'Feed' with the media type 'application/atom+xml'" message.
I tried different combinations of specifying MediaRangeMappings for the XmlMediaTypeFormatter (to be passed as an argument to ReadAsAsync) but with no success.
What is the "recommended" way to configure the HttpClient to map "application/atom+xml" and "application/rss+xml" to XmlSerializer?
Here is the code that works (credits to ASP.net forum thread):
public class AtomFormatter : XmlMediaTypeFormatter
{
public AtomFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/atom+xml"));
}
protected override bool CanReadType(Type type)
{
return base.CanReadType(type) || type == typeof(Feed);
}
}
var cli = new HttpClient();
cli
.GetAsync("http://stackoverflow.com/feeds/tag?tagnames=delphi&sort=newest")
.ContinueWith(task =>
{
task.Result.Content.ReadAsAsync<Feed>(new[] { new AtomFormatter });
});
Still, would like to see a solution without subclassing XmlMediaTypeFormatter - anybody?
The problem is that you are trying to convert the result straight to Feed. As error is clearly saying, it cannot figure our how to convert the application/atom+xml into Feed.
You would have to perhaps return as XML and then use and XmlReader to initialise your Feed.
Alternative is to provide your own media formatter - and implementation which encapsulates this.
I'm using Bingmaps API to get the AdminDistrict and CountryRegion of a Latitude and Longitude.
It works entering in a browser to this url:
http://dev.virtualearth.net/REST/v1/Locations/-30,-70/?includeEntityTypes=AdminDivision1,CountryRegion&o=xml&c=es-ES&key=myBingmapsApiKey
But in C# on WP7 I can't get get it work.
This is the code:
string wsUrl = "http://dev.virtualearth.net/REST/v1/Locations/-30,-70/?includeEntityTypes=AdminDivision1,CountryRegion&o=xml&c=es-ES&key=*myBingmapsApiKey*";
var request = new RestSharp.RestRequest(Method.GET);
var client = new RestSharp.RestClient(wsUrl);
try
{
RestSharp.RestResponse resource;
client.ExecuteAsync(request, (response) =>
{
resource = response;
string content = resource.Content;
string status_code = resource.StatusCode.ToString();
string response_status = resource.ResponseStatus.ToString();
});
}
catch (Exception e)
{
string error = "Error: " + e.ToString() + "\n. Stack Trace: " + e.StackTrace;
}
And the response is:
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
<Copyright>Copyright © 2011 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.</Copyright>
<BrandLogoUri>http://dev.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri>
<StatusCode>401</StatusCode><StatusDescription>Unauthorized</StatusDescription>
<AuthenticationResultCode>InvalidCredentials</AuthenticationResultCode>
<ErrorDetails><string>Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation.</string></ErrorDetails>
<TraceId>59ebcf604bb343d79a6e8b93ad5695fe|MIAM001452|02.00.71.1600|</TraceId>
<ResourceSets />
</Response>
The url is the same that is working on web browser.
What can be wrong?
probably at this point you already came up with a solution but searching on google i found this topic and the solution is to not send the key through the url like you doing, instead add it as parameter in the request like this:
string wsUrl = "http://dev.virtualearth.net/REST/v1/Locations/-30,-70/";
var request = new RestSharp.RestRequest(Method.GET);
request.AddParameter("includeEntityTypes", "AdminDivision1,CountryRegion");
request.AddParameter("key", myLey);
request.AddParameter("o", "xml");
If I understand this right then the REST API you use may cost money. Maybe your API key istn't set up for billable transactions?
The page says about billing of Location API:
*This category is not billable if it occurs within the context of an AJAX Control or Silverlight Control session.
Maybe the browser counts as AJAX control and the phone isn't exactly a "Silverlight Control".