Configure Twilio Webhooks automatically with Visual Studio dev tunnels during ASP.NET Core startup

November 07, 2022
Written by
Reviewed by

Configure Twilio Webhooks with Visual Studio dev tunnels during ASP.NET Core startup

In a previous blog post, I showed how you can use Visual Studio dev tunnels to make your ASP.NET Core application publicly available and then use your public application to handle the Twilio SMS webhook.

In this blog post, you will learn how to retrieve the tunnel URL from your ASP.NET Core application and use it to automatically update the Twilio SMS Webhook, so you don't have to do this manually whenever the tunnel URL changes.

Prerequisites

You'll need the following things to complete this tutorial:

You can find details on how to do these steps in the dev tunnels announcement post by Microsoft.

Lastly, you'll also need a Twilio account (trial or upgraded). If you register for a free Twilio account here, you'll get $20 in trial credit to try out Twilio products.

You can find the source code for this tutorial on GitHub (in the configure-voice-url branch). Use it as a reference if you run into any issues, or submit an issue if you need assistance

How to get the Visual Studio dev tunnels URL

Before you can configure the Twilio Webhooks, you'll need to retrieve the public URL of the Visual Studio dev tunnels. Luckily Visual Studio hands over this URL in a straightforward manner. When you start your ASP.NET Core project from Visual Studio, an environment variable named VS_TUNNEL_URL will be set with the URL of the tunnel.

Open the project from the previous tutorial in Visual Studio, and then update Program.cs with the following code:


using Twilio.AspNet.Core.MinimalApi;
using Twilio.TwiML;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/message", () =>
{
    var messagingResponse = new MessagingResponse();
    messagingResponse.Message("Ahoy!");
    return Results.Extensions.TwiML(messagingResponse);
});

await app.StartAsync();

var devTunnelUrl = Environment.GetEnvironmentVariable("VS_TUNNEL_URL");
if (!string.IsNullOrEmpty(devTunnelUrl))
{
    app.Logger.LogInformation("The tunnel URL is {TunnelUrl}", devTunnelUrl);
}

await app.WaitForShutdownAsync();

The first thing that has been changed is that instead of app.Run(), app.StartAsync() is used. This will start the application, but you can still run some code after the application has started. Once your code is finished running after starting the app, app.WaitForShutdownAsync() is used to keep the application running until shutdown is requested. This is not really required to get the tunnel URL, but this is where you'll update the SMS Webhook URL. It makes more sense to update the SMS webhook URL once the application has been started and is ready to accept HTTP requests.

Once the application is started, the program retrieves the tunnel URL using Environment.GetEnvironmentVariable("VS_TUNNEL_URL"). If the URL is not null or empty, it will be logged.

Start your project using Visual Studio and observe the output, it should say something like The tunnel URL is https://0pbvlk3m-7032.use.devtunnels.ms/.

Configure the Twilio Client to use the Twilio API

Now that you have the tunnel URL, you'll need to use the Twilio API to update the SMS Webhook URL for your Twilio Phone Number. The Twilio helper library for ASP.NET Core has already been installed into the project from the previous tutorial. Update Program.cs with the following code:


using Twilio.AspNet.Core.MinimalApi;
using Twilio.TwiML;
using Twilio.AspNet.Core;
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

var builder = WebApplication.CreateBuilder(args);

// add TwilioRestClient to Dependency Injection Container
// configured using 'Twilio:Client:AccountSid' and 'Twilio:Client:AuthToken'
builder.Services.AddTwilioClient();

var app = builder.Build();

app.MapPost("/message", () =>
{
    var messagingResponse = new MessagingResponse();
    messagingResponse.Message("Ahoy!");
    return Results.Extensions.TwiML(messagingResponse);
});

await app.StartAsync();

var devTunnelUrl = Environment.GetEnvironmentVariable("VS_TUNNEL_URL");
if (!string.IsNullOrEmpty(devTunnelUrl))
{
    using var serviceScope = app.Services.CreateScope();
    var twilioClient = serviceScope.ServiceProvider.GetRequiredService<ITwilioRestClient>();
}

await app.WaitForShutdownAsync();

The TwilioRestClient and ITwilioRestClient are added to the Dependency Injection (DI) container using builder.Services.AddTwilioClient(). Since these are scoped services, you do need to create a service scope to retrieve them which is what app.Services.CreateScope() does. Then, an instance of ITwilioRestClient is retrieved from the DI container, which will be used to communicate with the Twilio APIs.

Before you can use the Twilio client, you'll need to configure either your Twilio Account SID and Auth Token, or your Twilio Account SID, and an API Key SID and API Key Secret. To keep things simple, let's stick to using the Account SID and Auth Token, which you can find in the Twilio Console.

In Visual Studio, right-click on your project and click "Manage User Secrets". Then add the following secrets:

{
  "Twilio": {
    "Client": {
      "AccountSid": "[YOUR_ACCOUNT_SID]",
      "AuthToken": "[YOUR_AUTH_TOKEN]"
    }
  }
}

Replace [YOUR_ACCOUNT_SID] with your Twilio Account SID and [YOUR_AUTH_TOKEN] with your Twilio Auth Token.

Update the SMS Webhook URL with the Tunnel URL

Now that you have a Twilio client to interact with the Twilio API, add the highlighted code to retrieve your Twilio Phone Number and then update its SMS Webhook URL:


var devTunnelUrl = Environment.GetEnvironmentVariable("VS_TUNNEL_URL");
if (!string.IsNullOrEmpty(devTunnelUrl))
{
    using var serviceScope = app.Services.CreateScope();
    var twilioClient = serviceScope.ServiceProvider.GetRequiredService<ITwilioRestClient>();
    
    var phoneNumber = app.Configuration["Twilio:PhoneNumber"];
    if (string.IsNullOrEmpty(phoneNumber)) throw new Exception("'Twilio:PhoneNumber' not configured");

    var phoneNumberResources = await IncomingPhoneNumberResource.ReadAsync(
        phoneNumber: new PhoneNumber(phoneNumber),
        client: twilioClient
    );
    var phoneNumberResource = phoneNumberResources.First();

    var smsUrl = $"{devTunnelUrl}message";
    await IncomingPhoneNumberResource.UpdateAsync(
        pathSid: phoneNumberResource.Sid,
        smsUrl: new Uri(smsUrl),
        smsMethod: Twilio.Http.HttpMethod.Post,
        client: twilioClient
    );

    app.Logger.LogInformation("Updated {TwilioPhoneNumber} SMS URL to {SmsUrl}", phoneNumber, smsUrl);
}

To update the IncomingPhoneNumberResource you'll need to first get its String IDentified (SID). The code will retrieve the IncomingPhoneNumberResource by the phone number, grab the Sid and update the smsUrl and smsMethod to point to the public tunnel URL suffixed with message.

Lastly, to get that phone number from configuration, configure Twilio:PhoneNumber in appsettings.Development.json like this:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Twilio": {
    "PhoneNumber": "[YOUR_TWILIO_PHONE_NUMBER]"
  }
}

Replace [YOUR_TWILIO_PHONE_NUMBER] with your Twilio Phone Number in E.164 number formatting.

Test your SMS application

Now that everything has been set up, it's time to test out your SMS application. Pull out your personal phone, and send an SMS to your Twilio Phone Number. You should receive a response saying "Ahoy!".

Also take a look at the terminal output, which should say something like Updated +1234567890 SMS URL to https://0pbvlk3m-7032.use.devtunnels.ms/message.

Next steps

Congratulations on building this SMS application that automatically configures the SMS Webhook URL with the Visual Studio tunnel URL! You can use the same technique for other Twilio Webhooks. Since your application is now public, that also means anyone on the internet can reach it which comes with its own risks. To mitigate these risks, you can validate that the incoming requests originated from Twilio and not some bad actor.

Want to learn what else you can do with Twilio? Here are some more tutorials for inspiration:

Shout-out to Sayed I. Hashimi for answering my questions and taking my feedback.

Niels Swimberghe is a Belgian American software engineer and technical content creator at Twilio. Get in touch with Niels on Twitter @RealSwimburger and follow Niels’ personal blog on .NET, Azure, and web development at swimburger.net.