Integrate ngrok into ASP.NET Core startup and automatically update your webhook URLs
Time to read: 9 minutes
When you are developing web applications on your local machine, you sometimes need your application to be reachable from the internet. One of the most common reasons to do this is to develop webhooks.
Webhooks are a way to be notified by an external service when an event has occurred. Instead of you sending an HTTP request to that service, the service sends an HTTP request to your public web service.
To develop webhooks locally, you can use a tunnel service like ngrok which creates a tunnel between your local network and the internet. However, when you're using ngrok's free plan, ngrok will create a random, public URL anytime you restart your tunnel. This means that you need to update your webhooks with the new URL anytime it changes. If updating your webhook URLs takes a bunch of clicks and keystrokes, this can be quite a hassle.
Luckily, you can avoid the repetitive work by automating this! In this tutorial, you'll learn how to automatically start ngrok when your ASP.NET Core application starts. Then, you'll learn how to grab the random ngrok URL and use the URL to configure Twilio's webhooks automatically.
Prerequisites
You will need these items to follow along:
- An OS that supports .NET (Windows/macOS/Linux)
- .NET 6 SDK
- A code editor or IDE (Recommended: VS Code with the C# plugin, Visual Studio, or JetBrains Rider)
- The Ngrok CLI
- A free Ngrok account (optional)
- A free Twilio account
You can find the source code for this tutorial on GitHub. Use the source code if you run into any issues, or submit an issue on this GitHub repo if you run into problems.
Create an ASP.NET Core web project
Open your preferred shell, and use the commands below to create a new folder named NgrokAspNet, and navigate to it:
Use the .NET CLI to create a new empty web project:
Your new project contains one C# file, named Program.cs:
The program creates a new web application with a single endpoint responding with "Hello World". Head back to your shell and start the project using the .NET CLI:
The output should look like this:
Take note of the two localhost URLs chosen for you, when you generated the web project.
Pick one of the URLs and open it in a web browser. You should see "Hello World!" displayed in your browser.
Use ngrok to tunnel your local web server to the internet
You can run the ngrok CLI tool on your machine to tunnel the local URL to a public URL that looks similar to https://66a605a7ced5.ngrok.io, with a different subdomain. Every time you start a new tunnel using ngrok, the subdomain is different.
In your new shell, run the following command:
Replace [YOUR_HTTP_SERVER_URL] with the web server URL starting with http://localhost.
This will start a new tunnel to a new public URL that you can find in the displayed output:
Switch back to the web browser and navigate to one of the Forwarding URLs listed in your shell. Some browsers may warn you that this is a deceptive site, which you can disregard in this case. The browser should once again return "Hello World", but this time via the public URL. This means you can share this URL with anyone, and they will also be able to communicate with your local web server. This also means webhooks can reach your local server.
You can also tunnel HTTPS URLs, but you need to sign up for an ngrok account (free) and authenticate your ngrok CLI tool. Once you have done that, you can also run the ngrok http
command with the URL starting with https://localhost instead.
In addition to the forwarding URLs, there's also a Web Interface URL displayed. This is where you can access ngrok's local dashboard and API. Switch back to your browser and navigate to http://localhost:4040. Here you will find the forwarding URLs once again, and also a log of all the HTTP requests coming through the tunnel.
Stop the ngrok process by pressing ctrl + c and close this shell instance. Switch to your other shell and stop the .NET project by pressing ctrl + c.
Start ngrok automatically during ASP.NET Core startup
You were able to publicly serve your web application by running your ASP.NET project and then running ngrok in a separate shell. This workflow can be optimized by integrating ngrok into the startup process of your ASP.NET project.
To start the ngrok tunnel programmatically, you'll need to run the ngrok CLI command from code. You could use the Process
.NET APIs, but there's an open-source library that makes interacting with CLI tools and processes easier: CliWrap.
Add the CliWrap NuGet package using the .NET CLI:
Create a new C# file in the NgrokAspNet project directory, named TunnelService.cs, and add the following code:
The TunnelService
class will be responsible for starting a tunnel using the ngrok CLI, and later on it will also configure Twilio webhooks.
This is a lot of code, so let's dissect it piece by piece.
The constructor accepts multiple parameters that will be provided by the dependency injection container built into ASP.NET Core. All the parameters are stored in private fields, so they are accessible throughout the class.
- The
server
parameter contains information about the web server currently being started. Once the web server is started, you can retrieve the local URLs from theserver
field. - The
hostApplicationLifetime
parameter lets you hook into the different lifecycle events (started/stopping/stopped).
Theconfig
parameter will contain all the configuration passed into the .NET application through command-line arguments, environment variables, JSON files, user-secrets, etc. The config isn't used right now, but it will be used in an upcoming section. - The
logger
parameter will be used to log any information relevant to running the tunnel.
TunnelService
inherits from the abstract class BackgroundService
which is why you need to implement the abstract method ExecuteAsync
. ExecuteAsync
is the main method of this class and will be invoked as the web application is starting. ExecuteAsync
will wait for the web application to have started using WaitForApplicationStarted
, and then grab the local URLs. A single URL will be taken from the local URLs. If you authenticated ngrok earlier, you can use the HTTPS URL instead of the HTTP URL by replacing "http://"
with "https://"
.
Next, the ngrok tunnel will be started, then the public ngrok URL will be retrieved, and finally the Task
for running the ngrok CLI is awaited. When the web application stops, the ngrok process will also be stopped, which will complete the ngrokTask
.
WaitForApplicationStarted
will create an awaitable Task
that will be completed when the ApplicationStarted
event is triggered. Oddly, the lifecycle events on IHostApplicationLifetime
are not using delegates or C# events, but instead they are CancellationToken
's.
You can pass in a lambda or delegate to the CancellationToken.Register
method which will be invoked when the CancellationToken
is canceled. In case of the cancellation token stored in the IHostApplicationLifetime.ApplicationStarted
property, when the token is canceled, this means the application has started. How (un)intuitive, am I right?
To make this more intuitive to use, you can create a TaskCompletionSource
and set its result in the hostApplicationLifetime.ApplicationStarted.Register
callback. This will set the Task
as completed, which in this case will be when the application has started.
If all of this is a little confusing, the important thing to take away is that await WaitForApplicationStarted()
will wait for the web application to have started.
StartNgrokTunnel
will use the CliWrap library to run the ngrok CLI. The resulting command will look like this:
This command will start the ngrok tunnel like before, but with the addition of the --log stdout
argument. This log argument instructs ngrok to log to standard output which can then be captured through WithStandardOutputPipe
. The standard output and error output will be piped to the logger
.
A critical detail is that the stoppingToken
is passed to ExecuteAsync
. The stoppingToken
will be canceled when the application is being stopped. You can use this token to gracefully handle when the application is about to be shutdown. By passing the stoppingToken
to ExecuteAsync
, the ngrok process will also be stopped when the application is stopped. Thus, you won't have any ngrok child processes lingering around.
The GetNgrokPublicUrl
will fetch the public HTTPS URL and return it. You can get the public tunnel URLs by requesting it from the local ngrok API at http://127.0.0.1:4040/api/tunnels.
Unfortunately, when the ngrok CLI is started, that doesn't mean the tunnel is ready yet. That's why this code is surrounded in a loop that will try to get the public URL up to 10 times, every 200 milliseconds. Feel free to change the 200ms delay and the retryCount
to whatever suits your needs.
The TunnelService
class is complete, but you still need to configure the web application to run it in the background. Update Program.cs, based on the highlighted lines below:
The TunnelService
will be configured to run in the background, but only when the application is run in a development environment. After all, you only want to use the ngrok tunnel for local development.
That's it! Run the application using the .NET CLI and watch the output:
The public ngrok URL will be logged to the output like this: "Public ngrok URL: https://6797-72-66-29-154.ngrok.io". Grab the public ngrok URL and navigate to it in the browser. You will see, once again, "Hello World!".
Update Twilio Webhooks automatically with ngrok URLs
Get started with Twilio
If you haven't already, you'll need to set up the following with Twilio:
- Go and buy a new phone number from Twilio. The cost of the phone number will be applied to your free promotional credit.
Make sure to take note of your new Twilio phone number. You'll need it later on! - If you are using a trial Twilio account, you can only send text messages to Verified Caller IDs. Verify your phone number or the phone number you want to SMS if it isn't on the list of Verified Caller IDs.
- Lastly, you'll need to find your Twilio Account SID and Auth Token. Navigate to your Twilio account page and take note of your Twilio Account SID and Auth Token located at the bottom left of the page.
Update Twilio Phone Number Webhooks
The Twilio SDK for C# and .NET will help you interact with Twilio's APIs and respond to webhooks. Add the Twilio NuGet package to your project:
Go back to your code editor and open the TunnelService.cs file. Update the using
statements at the top of the file, to include these three new Twilio references:
Update the ExecuteAsync
method to invoke the asynchronous ConfigureTwilioWebhook
method after logging the public ngrok URL:
Add the asynchronous ConfigureTwilioWebhook
method after the GetNgrokPublicUrl
method:
The ConfigureTwilioWebhook
method receives the public ngrok URL as a parameter. The Account SID and Auth Token are retrieved from the config
field and passed into the constructor of TwilioRestClient
.
The Twilio phone number details are requested using TwilioRestClient
, and then the phone number details are used to update the voice webhook URL and the SMS webhook URL. The voice and SMS webhook URLs will be set to the public tunnel URL with /voice
and /message
appended to it, respectively.
The project now depends on the TwilioAccountSid
, TwilioAuthToken
, and TwilioPhoneNumber
configuration element, but they haven't been configured yet. You can use .NET user secrets to configure these types of sensitive configuration.
Initialize user secrets for your project using the .NET CLI:
Run the following command to configure the secrets:
Replace [YOUR ACCOUNT SID]
with your Twilio Account SID, [YOUR AUTH TOKEN]
with your Twilio Auth Token, and [YOUR TWILIO PHONE NUMBER]
with your Twilio Phone Number.
Test out your work so far by running the application:
You should see additional output that looks like this: "Twilio Phone Number +1234567890 Voice URL updated to https://5fb3-72-66-29-154.ngrok.io/voice" and "Twilio Phone Number +1234567890 Message URL updated to https://5fb3-72-66-29-154.ngrok.io/message"
Respond to Twilio webhooks
Once the webhook URLs are set, Twilio will send HTTP requests to your public URLs whenever a phone call or SMS goes to your Twilio phone number.
You need to accept these HTTP requests for /voice
and /message
, and then respond with TwiML instructions. Update Program.cs based on the highlighted lines in the code below:
When Twilio sends an HTTP POST request to /voice
, the endpoint will respond with the following TwiML:
As a result, Twilio will transcribe "Hello World!" to audio and stream it to the caller.
When Twilio sends an HTTP POST request to /message
, the endpoint will respond with the following TwiML:
As a result, Twilio will respond with a text message saying "Hello World!".
Testing the Twilio webhooks
If everything went well, you are now able to develop and test webhooks by running a single command dotnet run
. Start the application using the .NET CLI:
Wait for the webhook URLs to be updated, and then call and/or text your Twilio Phone Number.
If you call, you should hear "Hello World!", and if you text, you should receive a text message saying "Hello World!".
How to integrate ngrok into ASP.NET and automatically update your webhooks
In this tutorial, you learned how to streamline your webhook development process by integrating ngrok into your ASP.NET Core startup and automatically updating your webhooks, using these steps:
- Get your local ASP.NET URLs
- Use a
BackgroundService
to run the ngrok tunnel - Fetch the ngrok forwarding URL from ngrok's local API
- Update your webhooks URLs using the ngrok forwarding URL
Twilio has a lot of other products you can integrate into your applications. Check out this tutorial on how to make phone calls from Blazor WebAssembly with Twilio Voice and Twilio Client.
Additional resources
Check out the following resources for more information on the topics and tools presented in this tutorial:
TwiML for Programmable Voice – Learn more about TwiML and how you can use TwiML to handle phone calls.
TwiML for Programmable SMS – Learn more about TwiML and how you can use TwiML to respond to text messages.
Source Code for this tutorial on GitHub - Use this source code if you run into any issues, or submit an issue on this GitHub repo if you run into problems.
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.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.