This content originally appeared on Twilio Blog and was authored by Niels Swimberghe
Twilio's SMS and voice webhooks let you respond to messages and voice calls using TwiML (the Twilio Markup Language). You can implement these webhooks using any F# web framework, but in this tutorial, you'll use the built-in ASP.NET Core Minimal API template.
Prerequisites
Here’s what you will need to follow along:
- .NET 7 SDK (earlier and newer versions may work too)
- A code editor or IDE (I recommend JetBrains Rider, Visual Studio, or VS Code with the Ionide for F# plugin)
- A free Twilio account (sign up with Twilio for free and get trial credit to try out Twilio products)
- A Twilio phone number
- The ngrok CLI and, optionally, a free ngrok account
You can find the source code for this tutorial on GitHub. Use it if you run into any issues, or submit an issue if you run into problems.
Set up your F# Minimal API project
Open your preferred shell and use the .NET CLI to create a new empty ASP.NET Core project with the F# language argument:
dotnet new web -o FsharpTwilioWebhook --language F#
cd FsharpTwilioWebhook
Twilio provides some libraries to speed up the development of your Twilio applications, which you'll use for this project. Add the Twilio helper library for .NET and the Twilio helper library for ASP.NET Core using the following commands:
dotnet add package Twilio
dotnet add package Twilio.AspNet.Core
Now you are ready to write some code!
Implement the SMS and Voice webhook
When implementing the SMS or voice webhook, you're expected to respond with TwiML, which instructs Twilio how to respond to a message or call.
Open your project using your preferred IDE and update the Program.fs file with the following code:
open System
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Twilio.AspNet.Core
open Twilio.TwiML
[<EntryPoint>]
let main args =
let builder = WebApplication.CreateBuilder(args)
let app = builder.Build()
app.MapPost("/message", Func<TwiMLResult>(
fun () -> MessagingResponse().Message("Ahoy!").ToTwiMLResult())
) |> ignore
app.MapPost("/voice", Func<TwiMLResult>(
fun () -> VoiceResponse().Say("Ahoy!").ToTwiMLResult())
) |> ignore
app.Run()
0 // Exit code
Start your application using this command:
dotnet run
Your application will print out the localhost URL(s) the web server is listening to, including the port number. In the upcoming commands, replace [YOUR_LOCALHOST_URL]
with one of those localhost URLs.
To quickly test this without configuring your Twilio phone number, use the following cURL commands or PowerShell:
curl -X POST [YOUR_LOCALHOST_URL]/message
curl -X POST [YOUR_LOCALHOST_URL]/voice
Invoke-WebRequest [YOUR_LOCALHOST_URL]/message -Method Post
Invoke-WebRequest [YOUR_LOCALHOST_URL]/voice -Method Post
You should get the following TwiML as the responses:
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Message>Ahoy!</Message>
</Response>
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Say>Ahoy!</Say>
</Response>
Configure the webhooks on your Twilio Phone Number
To see how this would behave in your Twilio application, you'll need to configure the message and voice webhook on your Twilio phone number. But before you can do that, you'll need to make your locally running project accessible to the internet. You can quickly do this using ngrok which creates a secure tunnel to the internet for you.
Leave your .NET application running, and run the following command in a separate shell:
ngrok http [YOUR_LOCALHOST_URL]
ngrok will print the Forwarding URL, which you'll need to publicly access your local application.
Now, go to the Twilio Console in your browser, use the left navigation to navigate to Phone Numbers > Manage > Active Numbers, and then select the Twilio phone number you want to test with. (If Phone Numbers isn't on the left pane, click Explore Products and then on Phone Numbers.)
Then, on the phone number configuration page, locate the "A CALL COMES IN" section. Underneath that:
- set the first dropdown to Webhook,
- set the text box next to it to the ngrok forwarding URL — adding on the /voice path,
- and set the dropdown after the text box to "HTTP POST".
Follow the same steps at the "A MESSAGE COMES IN" section, but use the /message path instead. Then, click Save.
Now, call and text the Twilio phone number. When calling, you should hear "Ahoy". When texting, you should receive a response like "Ahoy!".
Read the incoming message and call details
Responding with a hard-coded message is a great first step, but you'd probably want to respond dynamically instead. Let's add some code to read the incoming message body and repeat it back.
Update the Program.fs file:
open System
open System.Threading
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Hosting
open Twilio.AspNet.Core
open Twilio.TwiML
[<EntryPoint>]
let main args =
let builder = WebApplication.CreateBuilder(args)
let app = builder.Build()
app.MapPost("/message", Func<HttpRequest, CancellationToken, Task<TwiMLResult>>(fun request ctx ->
task {
let! form = request.ReadFormAsync(ctx).ConfigureAwait(false)
let body = form["Body"]
return MessagingResponse().Message($"You said: {body}!").ToTwiMLResult()
})
) |> ignore
app.MapPost("/voice", Func<TwiMLResult>(
fun () -> VoiceResponse().Say("Ahoy!").ToTwiMLResult())
) |> ignore
app.Run()
0 // Exit code
Now, the /message endpoint reads the data that is passed in as a form and retrieves the Body
parameter, which holds the body of the text message. That body is then echoed back to the sender.
Let's also make the /voice webhook more interesting by asking a question and then responding differently based on the answer. Update the code as shown below:
open System
open System.Threading
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Hosting
open Twilio.AspNet.Core
open Twilio.TwiML
[<EntryPoint>]
let main args =
let builder = WebApplication.CreateBuilder(args)
let app = builder.Build()
app.MapPost("/message", Func<HttpRequest, CancellationToken, Task<TwiMLResult>>(fun request ctx ->
task {
let! form = request.ReadFormAsync(ctx).ConfigureAwait(false)
let body = form["Body"]
return MessagingResponse().Message($"You said: {body}!").ToTwiMLResult()
})
) |> ignore
app.MapPost("/voice", Func<TwiMLResult>(fun () ->
VoiceResponse()
.Say("Which is better? Press 1 for cake, 2 for pie.")
.Gather(
action = Uri("/voice/response", UriKind.Relative),
numDigits = 1
)
// redirect back to current endpoint if no response
.Redirect(Uri("/voice", UriKind.Relative))
.ToTwiMLResult()
)) |> ignore
app.MapPost("/voice/response", Func<HttpRequest, CancellationToken, Task<TwiMLResult>>(fun request ctx ->
task {
let! form = request.ReadFormAsync(ctx).ConfigureAwait(false)
let result = form["Digits"].ToString()
return VoiceResponse()
.Say(if result = "1" then "The cake is a lie."
else if result = "2" then "Yum, pie."
else "You do you.")
.ToTwiMLResult()
})
) |> ignore
app.Run()
0 // Exit code
Stop the application by pressing ctrl + c and start it again using dotnet run
.
Text and call your Twilio number to try out the new functionality.
Secure your webhooks
Now that your web application is publicly accessible, it's also accessible to malicious actors. Luckily, the Twilio helper library for ASP.NET Core has a ValidateTwilioRequestFilter
that validates that the HTTP request originates from Twilio.
Update Program.fs as below to use it:
open System
open System.Threading
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.HttpOverrides
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Twilio.AspNet.Core
open Twilio.TwiML
[<EntryPoint>]
let main args =
let builder = WebApplication.CreateBuilder(args)
builder.Services.AddTwilioRequestValidation() |> ignore
builder.Services.Configure<ForwardedHeadersOptions>(
fun (options: ForwardedHeadersOptions) -> options.ForwardedHeaders <- ForwardedHeaders.All
) |> ignore
let app = builder.Build()
app.UseForwardedHeaders() |> ignore
let twilioEndpoints = app.MapGroup("").ValidateTwilioRequest()
twilioEndpoints.MapPost("/message", ...
) |> ignore
twilioEndpoints.MapPost("/voice", ...
)) |> ignore
twilioEndpoints.MapPost("/voice/response", ...
) |> ignore
app.Run()
0 // Exit code
The code creates an endpoint group for all the Twilio related endpoints, and adds ValidateTwilioRequestFilter
as an EndpointFilter
for the group. As a result, any requests that did not originate from Twilio will get a 403 Forbidden
response. Otherwise, the endpoint will handle the request as before.
The request validation uses the URL of the incoming request as part of the validation check. The forwarded headers middleware is used to make sure the request URL uses the ngrok forwarding URL and not the localhost URL.
The request validator will use the auth token configured at Twilio:RequestValidation:AuthToken
. Since the Auth Token is a secret that you should not share, you should avoid hard-coding it, such as putting it in your appsettings.json file, or any other way it could end up in your source control history. Instead, use the Secrets Manager aka user-secrets, environment variables, or a vault service.
Run the following commands to initialize user-secrets:
dotnet user-secrets init
Then grab the Auth Token from your Twilio Account and set it using this command:
dotnet user-secrets set Twilio:RequestValidation:AuthToken [YOUR_AUTH_TOKEN]
When you restart your application, everything should continue to work, but any HTTP request not coming from Twilio or your localhost will be rejected.
Next steps
You learned how to read incoming SMS and calls, how to respond to them, and how to secure your Twilio webhooks, all using F# and ASP.NET Core Minimal APIs.
In addition to receiving SMS and responding to incoming SMS, you can also send SMS from your application. Learn how to send SMS from F# using Twilio.
Here are a couple more resources to further your learning on ASP.NET Core and Twilio:
- Forward Voicemails with Transcript to your Email using C# and ASP.NET Core
- Find your U.S. Representatives and Congressional Districts with SMS and ASP.NET Core
- Respond to Twilio Webhooks using Azure Functions
We can't wait to see what you build. Let us know!
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.
This content originally appeared on Twilio Blog and was authored by Niels Swimberghe
Niels Swimberghe | Sciencx (2023-02-08T11:00:09+00:00) Respond to SMS and Voice webhooks using F# and Minimal APIs. Retrieved from https://www.scien.cx/2023/02/08/respond-to-sms-and-voice-webhooks-using-f-and-minimal-apis/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.