Use Azure Functions to Receive Twilio Webhooks and persist in Blob Storage

Copenhagen based cloud developer. Love to code and to make complex solutions simple!
Search for a command to run...

Copenhagen based cloud developer. Love to code and to make complex solutions simple!
No comments yet. Be the first to comment.
This series will cover lots of practical and hard learned experiences developing Azure Functions. Read about how to design your microservice setup, how to implement the functions using triggers.
...and how to use HttpClient correctly!
And examples for .NET projects also

...and why Windows is not going to sleep when left idle

TL;DR Keep trying the firmware update - after around 30 attempts doing the same thing, it just worked 🤯🎉🪅 Awesome Ultrawide Monitor, not only for gaming For once I'm going to write an article which is not directly related to programming, but if yo...

When using GitHub actions it's pretty neat that you can use the hosted GitHub runners. However, this can get costly if you use a lot of runners and they are not always that fast at running the actions. Luckily you can also host your own runners and w...

...and some SonarCloud code analysis!

There are a few ways to develop Azure Functions. In this post we are going to use Visual Studio to develop the function, but we could also have choosen to use the Azure Portal. Since I don't like to code in a browser, I prefer Visual Studio over the Portal!
There are a few steps involved, but it's actually super simple, and this didn't take much more than ½ an hour to get done, so read along.
Begin by creating a new Azure Function in Visual Studio. You will be asked to define the trigger, selet Http trigger like so:

First thing to do is to configure the HttpTrigger attribute. Twilio will only POST to your function, so you can delete get from the triggers params methods. Since we will persist the webhook data to Azure Storage, we also want to create an output binding to a CloudBlobContainer, add the Blob attribute, pass the blobPath (which is the container name, point to a Connection defined in local.settings.json (more on that later). Your function signature should look like this:
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Blob("twilio-webhooks", Connection = "AzureWebJobsStorage")] CloudBlobContainer outputContainer, ILogger log)
Now we are going to do the following:
// Read the request body
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogDebug($"Request body: {requestBody}");
// Twilio sends form data in the body, the below parses this into JSON. Here's Twilio's doc: https://www.twilio.com/docs/usage/webhooks/sms-webhooks
var formValues = requestBody.Split('&')
.Select(value => value.Split('='))
.ToDictionary(pair => Uri.UnescapeDataString(pair[0]),
pair => Uri.UnescapeDataString(pair[1]));
var jsonObj = JsonConvert.SerializeObject(formValues, Formatting.Indented);
log.LogDebug($"Request parsed to JSON: {JsonConvert.SerializeObject(jsonObj)}");
// Now persist in Azure Storage
// This can be omitted
await outputContainer.CreateIfNotExistsAsync();
// Create a blob name like: 2020-12-18 20-45-12 59A0962E-2AE9-498C-8B50-FE3F5935B556.json
var blobName = $"{DateTime.UtcNow:yyyy-MM-dd HH-mm-ss}-{Guid.NewGuid()}.json";
var cloudBlockBlob = outputContainer.GetBlockBlobReference(blobName);
await cloudBlockBlob.UploadTextAsync(jsonObj);
And finally, which is SUPER IMPORTANT return 200 OK to Twilio. If you don't do that in a timely manner or if your function returns an error, Twilio might stop sending you webhooks. I have implemented a pattern to handle this by adding a second layer of abstration and throwing an Azure Queue in between the receiving and processing part of this, but more on that in another post.
return new OkResult();
Here are all the pieces put nicely together:
public static class WebhookFunctions
{
[FunctionName("TwilioWebhook")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Blob("twilio-webhooks", Connection = "AzureWebJobsStorage")] CloudBlobContainer outputContainer, ILogger log)
{
log.LogInformation("TwilioWebhook trigger function processing a request.");
// Read the request body
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogDebug($"Request body: {requestBody}");
// Twilio sends form data in the body, the below parses this into JSON. Here's Twilio's doc: https://www.twilio.com/docs/usage/webhooks/sms-webhooks
var formValues = requestBody.Split('&')
.Select(value => value.Split('='))
.ToDictionary(pair => Uri.UnescapeDataString(pair[0]),
pair => Uri.UnescapeDataString(pair[1]));
var jsonObj = JsonConvert.SerializeObject(formValues, Formatting.Indented);
log.LogDebug($"Request parsed to JSON: {JsonConvert.SerializeObject(jsonObj)}");
// Now persist in Azure Storage
// This can be omitted
await outputContainer.CreateIfNotExistsAsync();
// Create a blob name like: 2020-12-18 20-45-12 59A0962E-2AE9-498C-8B50-FE3F5935B556.json
var blobName = $"{DateTime.UtcNow:yyyy-MM-dd HH-mm-ss} {Guid.NewGuid()}.json";
var cloudBlockBlob = outputContainer.GetBlockBlobReference(blobName);
await cloudBlockBlob.UploadTextAsync(jsonObj);
return new OkResult();
}
}
Before testing locally, please make sure to put the AzureWebJobsStorage config value in the local.settings.json, like this:
{
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProt..."
...
}
}
Now it's time to test it out, hit F5 to debug your function and you should see something like this: Debuggin the function Simply fire up Fiddler or Postman, do a POST request to your functions url (localhost, some port, see the console window). Then, remember Twilio will send form encoded data, so you need to do the same when testing it. In Fiddler, that looks something like this:

Simply fire up Fiddler or Postman, do a POST request to your functions url (localhost, some port, see the console window). Then, remember Twilio will send form encoded data, so you need to do the same when testing it. In Fiddler, that looks something like this:

Let the function process the request and you should end up with something like this in Azure Storage Explorer:

It's simple to deploy the function, just remember to put the AzureWebJobsStorage in the App Settings in the Function app.
After deployment, you can get the function url from the Azure portal, copy this url, go to your Twilio page and paste it in there to let Twilio know to send webhooks your way.
That's it, please let me know what you think in the comments, and feel free to ask questions, if you have any. Thanks for reading!