Find your US Representatives and Congressional Districts with SMS and ASP.NET Core
Tags: dotnet, csharp, aspnet, twilio
Published: 2022-08-04 02:44:54

---
title: Find your US Representatives and Congressional Districts with SMS and ASP.NET Core
published: true
date: 2022-08-02 00:00:00 UTC
tags: dotnet, csharp, aspnet, twilio
canonical_url: https://www.twilio.com/blog/find-us-representatives-congressional-districts-with-sms-aspnetcore
cover_image: https://swimburger.net/media/nc2lscbk/build-an-sms-bot-to-find-representatives-congressional-districts-with-asp-net-core.png?anchor=center&mode=crop&width=1000&height=420
---

**This blog post was written for [Twilio](https://www.twilio.com/) and [originally published at the Twilio blog](https://www.twilio.com/blog/find-us-representatives-congressional-districts-with-sms-aspnetcore).**

When someone runs for office in the United States of America (U.S.), they have to fulfill certain "Ballot Access Requirements" to become listed on the ballot. One of those requirements could be that you need a certain number of signatures per district. So when volunteers and campaign workers go out to gather signatures, it is instrumental to know which congressional district (CD) the signee belongs to. However, knowing which congressional district you are from is not common knowledge, especially in areas where the district cuts right through counties, cities, and neighborhoods. 

States redraw their congressional districts every decade after getting an updated population count from the Census Bureau. Each district is represented by a single U.S. representative in the U.S. House of Representatives. However, the manner in which these districts are drawn often result in unique and odd shapes which can be confusing.  This is why constituents living just a street apart sometimes live in different districts, and as a result, vote in different house races. 

This blog post won't discuss why U.S. congressional district maps are drawn the way they are, however, I highly recommend you look into this topic and learn how it affects political voting power.

When I gathered signatures, the first question I had to ask over and over again was "what congressional district are you from?", and 95% of the time, the answer was "I don't know". That's because in Northern Virginia, the border between the 10th and 11th congressional district cuts directly through some counties, cities, neighborhoods, and streets. 

![Map of Virginia's 11th Congressional District](https://twilio-cms-prod.s3.amazonaws.com/images/BbH8CY4qRX1VgWmjvjnHZ7VONqfP3_ezHIH_L1hA0yauWzJ.original.png)
_Map of Virginia's 11th Congressional District_

The house.gov website has a [tool to look up your congressional district and representative](https://www.house.gov/representatives/find-your-representative), but unfortunately, the tool is slow and painful to use, especially when you're on the go on mobile internet.

That's why instead of using web technology, I built a solution using connectivity technology that's more accessible, more reliable, and simpler. Using [Twilio SMS](https://www.twilio.com/docs/sms/api), I built a phone number that you can text your address to and you will receive a response with your congressional district and representative. Let me walk you through how you can build this SMS bot using Twilio, C# .NET, and [Google's Civic Information API](https://developers.google.com/civic-information).

## Prerequisites

You'll need a couple of things to follow along:

- [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) (older or newer should work too)
- A code editor or IDE (I recommend [VS Code](https://code.visualstudio.com/Download) with [the C# plugin](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp), [Visual Studio](https://visualstudio.microsoft.com/), or [JetBrains Rider](https://www.jetbrains.com/rider/))
- The [ngrok CLI](https://ngrok.com/download), and optionally, a free [ngrok account](https://dashboard.ngrok.com/signup) 
- A Twilio account (If you [register for a Twilio account here](https://www.twilio.com/referral/OhKZOM), you'll receive $10 in Twilio credit when you upgrade to a paid account!)
- [A Twilio Phone Number](https://support.twilio.com/hc/en-us/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console)
- A personal phone or [the Twilio Dev Phone to test your bot](https://www.twilio.com/blog/test-sms-and-phone-call-applications-with-twilio-dev-phone)
- [A free Google Cloud account](https://cloud.google.com/docs/get-started)

You can find [the source code for this tutorial on GitHub](https://github.com/Swimburger/UsRepresentativeLookup). Use it as a reference if you run into any issues, or [submit an issue](https://github.com/Swimburger/UsRepresentativeLookup/issues) if you need assistance.

## Get Started with the Google Civic Information API

This SMS bot will use the [Google Civic Information API](https://developers.google.com/civic-information) to look up the U.S. congressional districts and representatives. This API has a free tier as part of the Google Cloud Platform.

Go to the [Google Cloud Console](https://console.cloud.google.com/) and [create a new project](https://cloud.google.com/resource-manager/docs/creating-managing-projects). Once the project is created, select the project using the project dropdown in the top-left navigation bar.

You'll first need to enable the Civic Information API. Click on "ENABLE APIS AND SERVICES", then search for the " **Civic Information API**".

![The "Enabled APIs & services" page in Google Cloud Platform. The page has a button to "ENABLE APIS AND SERVICES".](https://twilio-cms-prod.s3.amazonaws.com/images/weqzRsMyJFP3ADHDxzkMX1fJiCNI4QoLUHuGS9a1xXUQvS.width-500.png)

Click on the search result and then click **ENABLE**.

![The API listing for the "Google Civic Information API" with an enable button.](https://twilio-cms-prod.s3.amazonaws.com/images/mnWiRUPNE2VyU-rRC5QaYlKcdIU8BIYAxETsaggVKMwqkx.width-500.png)

Now that the API is enabled for your project, you'll need to create an API token to be able to authenticate when consuming the API from your .NET project. Navigate to the Credentials page, click on " **CREATE CREDENTIALS**" and then on " **API key**".

![GCP Credentials page where the user clicks on the "CREATE CREDENTIALS" button and then on the "API key" menu item.](https://twilio-cms-prod.s3.amazonaws.com/images/e8u-22_2WDeSs3NTxqo5CpKy-jABTmgjRaSwbVPgM_PHGk.width-500.png)

A modal will appear showing your API key. You will need this API key later, so copy and paste the API key somewhere safe. Next, click on the " **Edit API key**" link.

![API key created modal with a copy button to copy the key and a link "Edit API key".](https://twilio-cms-prod.s3.amazonaws.com/images/q5lY2i7PdnIUZmJFESAYXGwwj4oOOW2MH8tuq0KPrnIGOg.width-500.png)

On the "​​ **Edit API key**" page, you can configure the name and restrictions for your API key. This step isn't strictly required, but it is a good practice to follow the principle of least privilege, which means you should only give accounts and keys the level of access necessary to do their job.

In that spirit, find the " **API restrictions**" field and click on the " **Restrict key**" radio button.

Click on the newly appeared dropdown and select the " **Google Civic Information API**" option, and then hit **Save**.

![API restrictions form where the "Restrict key" radio button is enabled and the "Google Civic Information API" is selected in the dropdown.](https://twilio-cms-prod.s3.amazonaws.com/images/E4_t56z1NRDodfjVSedz3uGRItJfM1Acljn0YmGXCx8NUC.width-500.png)

You're all done setting up your GCP project and API key. Let's go try out the API!

## Try out the Civic Information API

Open a new tab in your browser and navigate to the [Civic Information API documentation for getting representatives by address](https://developers.google.com/civic-information/docs/v2/representatives/representativeInfoByAddress).The `representativeInfoByAddress` will be the specific method that you'll be using for this project.

You can pass a couple of parameters to the method to filter down the representative information, most importantly the required `address` field. This `address` field expects a physical mail address, however, it can be a partial address. You can pass in just the ZIP code, the city and state, or a full address including the street and number.

The HTTP response will contain `divisions`, `offices`, and `officials` for the given address:

- `divisions` are the political geographic areas that the address is within. Among these you would find the U.S. congressional districts.
- `offices` are the political offices someone can be elected to within the returned `divisions`.
- `officials` are the elected officials holding the returned `offices`.

By default, `representativeInfoByAddress` will return all divisions, offices, and officials that represent the given address, all the way from the highest office of the President down to the local offices.   
To filter this down, you can pass in the `levels` and `roles` parameters. 

This bot will look for U.S. representatives, so to filter down to only return representatives, you'll need to pass in `country` into the `levels` parameter because the U.S. representative is a national office, and you'll need to pass in `legislatorLowerBody` to the `roles` parameter because U.S. Congress has two houses: the Senate is the upper body and the House of Representatives is the lower body.

Click the "Try it now" link at the top of the page and play around with the method.

Set the `address` field to `10600 Little Run Farm Ct, Vienna`, the `levels` to `country`, and `roles` to `legislatorLowerBody`, and then click EXECUTE.

![A form to try out the representativeInfoByAddress API method. The address field has an address, the levels field is set to country, and the roles field is set to legislatorLowerBody. The API result shows the details of a single U.S. Representative.](https://twilio-cms-prod.s3.amazonaws.com/images/8pHGzoovtnsUIajKG-CaVss9k2ADDuHoJBf6PdPm_rcwWU.width-500.png)

The response should have a single division, office, and official.

New congressional district maps are going into effect for the November 2022 elections, so the maps and data will change soon!

Now change the `address` field to `Little Run Farm Ct, Vienna` and click EXECUTE again.  
This time no division, office, or official is returned. Why is that?

When you look up this street and overlay the congressional district maps, you can see that the border of district 10 and 11 cuts through this street. In fact, the border follows a creek that runs between house number 10600 and 10602.

![A map with Virginia"s Congressional District 11 overlayed. On the map, you can see two houses on the street Little Run Farm Ct are part of District 10, and the rest of the street is part of District 11.](https://twilio-cms-prod.s3.amazonaws.com/images/jgl5iGwYPZmPklt5wPouBIBXAwnTgg2nmwuv77DgVM49ge.width-500.png)

That's why the Civic Information API cannot determine the district when you pass in the street without a house number, and as a result, `representativeInfoByAddress` returns nothing.

The house.gov representative lookup tool will claim that house number 10600 and 10602 are both in Virginia's 11th congressional district which is incorrect (for the pre-2022 maps which are current at the time of writing this). Luckily, the Civic Information API returns the correct result.

This does not mean you always have to enter a full address, in fact when you put in the ZIP code 20301 without anything else, `representativeInfoByAddress` returns the information for the 8th congressional district. That's because the entire ZIP code is located within district 8, so the API doesn't need more information to determine the district.

Now that you're more familiar with the Civic Information API and specifically the `representativeInfoByAddress` method, it's time to consume the API from .NET!

## Build an SMS bot to look up representatives

This project will be built on ASP.NET Core Minimal APIs. Open a shell and run the following command to create the project and navigate into the project folder:

```bash
dotnet new web -o RepresentativeBot
cd RepresentativeBot
```

Open the project in your preferred editor.

### Retrieve Representatives from the Civic Information API

You can consume the Civic Information API using an HTTP client, but Google generates .NET libraries for all of its APIs including this one, which is convenient. You can install the library by adding the [Google.Apis.CivicInfo.v2 NuGet package](https://www.nuget.org/packages/Google.Apis.CivicInfo.v2)using the following command:

```bash
dotnet add package Google.Apis.CivicInfo.v2
```

Create a new file _RepresentativeLookupClient.cs_ and add the following C# code:

```csharp
using System.Net;
using Google;
using Google.Apis.CivicInfo.v2;
using Google.Apis.CivicInfo.v2.Data;
using Google.Apis.Services;
using static Google.Apis.CivicInfo.v2.RepresentativesResource;

namespace RepresentativeBot;

public class RepresentativeLookupClient
{
    private readonly CivicInfoService service;

    public RepresentativeLookupClient(string gcpApiKey)
    {
        service = new CivicInfoService(new BaseClientService.Initializer
        {
            ApplicationName = "US Representative Lookup",
            ApiKey = gcpApiKey
        });
    }

    public async Task<Representative> GetRepresentativeByAddress(string address)
    {
        var request = new RepresentativeInfoByAddressRequest(service)
        {
            Address = address,
            // Level = Country and Roles = LegislatorLowerBody filters down to U.S. Representatives
            Levels = RepresentativeInfoByAddressRequest.LevelsEnum.Country,
            Roles = RepresentativeInfoByAddressRequest.RolesEnum.LegislatorLowerBody
        };

        RepresentativeInfoResponse response;
        try
        {
            response = await request.ExecuteAsync();
        }
        catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.BadRequest &&
                                           e.Error.Message == "Failed to parse address")
        {
            throw new FailedToParseAddressException(e);
        }

        // if the address did not resolve to a specific district,
        // for example, no district, or multiple districts
        // then response.Offices will be null
        if (response.Offices == null)
        {
            throw new RepresentativeNotFoundException();
        }

        // only one office, one division, and one official will be returned
        // the office of U.S. representative, the congressional district, and the elected official
        var office = response.Offices[0];
        var division = response.Divisions[office.DivisionId];
        var official = response.Officials[0];
        return new Representative
        {
            DistrictName = division.Name,
            RepresentativeName = official.Name,
            Party = official.Party,
            PhotoUrl = official.PhotoUrl
        };
    }
}
```

The `RepresentativeLookupClient` constructor accepts the GCP API key as the `gcpApiKey` parameter, which is then used to create a new `CivicInfoService` object that is stored into the `service` field.

The `GetRepresentativeByAddress` method accepts an `address` as a parameter and returns a `Task` that will resolve to a `Representative` object. In this method, a `RepresentativeInfoByAddressRequest` object is created passing in the `service` into the constructor and the `address` into the `Address` property.  
The `Levels` and `Roles` property are hard-coded to `Country` and `LegislatorLowerBody` to filter down to U.S. representatives and congressional districts.

The API request is made using the `request.ExecuteAsync` method, which is surrounded by a try/catch block to handle some of the exceptions that can be thrown. When the API fails to parse the address, a custom exception of type `FailedToParseAddressException` is thrown. When no offices are returned, because the API couldn't determine the correct congressional district, or for some other reason, then a custom exception of `RepresentativeNotFoundException` is thrown. These two custom exceptions will be caught later to provide user-friendly messages to the end user.

However, if an office is returned, then a new `Representative` object is created with the data from the `response`.

The `Representative` object only has a couple of properties, however, feel free to extend the class and retrieve more information from the `response` object. The API retrieves a bunch of [information](https://developers.google.com/civic-information/docs/v2/representatives/representativeInfoByAddress#response) like a physical address, an email address, a phone number, social media links, etc.

For example, here's [a tutorial that uses the same API to retrieve contact information of the representatives and uses Twilio SMS and SendGrid to contact them, built on Twilio Studio and Functions](https://www.twilio.com/blog/sms-service-contact-local-representatives-twilio-studio-sendgrid).  

Create 3 new files named _Representative.cs_, _FailedToParseAddressException.cs_, and _RepresentativeNotFoundException.cs_ and then update each file with the contents listed below.

_Representative.cs_:

```csharp
namespace RepresentativeBot;

public class Representative
{
    public string DistrictName { get; set; }
    public string RepresentativeName { get; set; }
    public string Party { get; set; }
    public string PhotoUrl { get; set; }
}
```

_FailedToParseAddressException.cs_:

```csharp
namespace RepresentativeBot;

public class FailedToParseAddressException : Exception
{
    public FailedToParseAddressException(Exception innerException) : base(innerException.Message, innerException)
    {
    }
}
```

_RepresentativeNotFoundException.cs_:

```csharp
namespace RepresentativeBot;

public class RepresentativeNotFoundException : Exception
{
}
```

To use the `RepresentativeLookupClient` in your future API endpoints, you'll need to add it to ASP.NET Core's Dependency Injection (DI) container. Open your _Program.cs_ file and add the highlighted lines to the existing code:

```csharp
using RepresentativeBot;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped(provider =>
{
    var configuration = provider.GetRequiredService<IConfiguration>();
    var gcpApiKey = configuration["GcpApiKey"] ?? throw new Exception("GcpApiKey is not configured.");
    return new RepresentativeLookupClient(gcpApiKey);
});

var app = builder.Build();
...
```

The GCP API key you created earlier needs to be configured using the `GcpApiKey` configuration key. Since the API key is a secret, you should avoid hard-coding it or storing it in source control.You can use environment variables or a secure vault service, or for local development, you can use the .NET Secrets Manager also known as user secrets.

Run the following command to initialize user secrets in your project:

```bash
dotnet user-secrets init
```

Next, configure the GCP API key as a user secret:

```bash
dotnet user-secrets set GcpApiKey "[YOUR_GCP_API_KEY]"
```

Replace `[YOUR_GCP_API_KEY]` with the API key secret you copied earlier.

The `RepresentativeLookupClient` is now ready to be used! Feel free to add some code to quickly test it out, otherwise, move to the next step where you'll integrate the client into your minimal API.

### Create your Twilio Webhook

There's a useful library called `Twilio.AspNet` which will help you build Twilio webhooks.  
Add the [Twilio.AspNet.Core NuGet package](https://www.nuget.org/packages/Twilio.AspNet.Core/) to install the library:

```bash
dotnet add package Twilio.AspNet.Core
```

Then, open your _Program.cs_ file and update it with the following code:

```csharp
using RepresentativeBot;
using Twilio.AspNet.Core.MinimalApi;
using Twilio.TwiML;
using Twilio.TwiML.Messaging;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped(provider =>
{
    var configuration = provider.GetRequiredService<IConfiguration>();
    var gcpApiKey = configuration["GcpApiKey"] ?? throw new Exception("GcpApiKey is not configured.");
    return new RepresentativeLookupClient(gcpApiKey);
});

var app = builder.Build();

app.MapPost("/message", async (
    HttpRequest request,
    HttpResponse response,
    IServiceProvider serviceProvider,
    ILogger<Program> logger
) =>
{
    var messagingResponse = new MessagingResponse();

    if (bool.Parse(request.Cookies["HasBeenGreeted"] ?? "False") == false)
    {
        response.Cookies.Append("HasBeenGreeted", "True");
        messagingResponse.Message("Welcome to the U.S. Representative lookup bot. Respond with your address.");
        return Results.Extensions.TwiML(messagingResponse);
    }
    
    // TODO: remove me
    messagingResponse.Message("YOU HAVE BEEN GREETED ALREADY!");

    return Results.Extensions.TwiML(messagingResponse);
});

app.Run();
```

The new _/message_ endpoint accepts a couple of parameters that will be dependency injected.  
The `MessagingResponse` object will help you construct [TwiML](https://www.twilio.com/docs/glossary/what-is-twilio-markup-language-twiml) to respond to an incoming message. 

The first message you will respond with, is an introductory message. To only introduce the bot once, a cookie is used to keep track of whether the sender has been greeted with the introduction. 

You can use cookies when responding to Twilio webhooks, however, the cookie will expire after 4 hours and you can only store one cookie. In this tutorial you only need to keep track of one thing using cookies, but if you need to keep track of more state you can use a session cookie instead and store state in session. Learn more about [the cookie limitations with Twilio webhooks here](https://support.twilio.com/hc/en-us/articles/223136287-How-do-Twilio-cookies-work-).

The `messagingResponse.Message` method is used to create a TwiML `Message` verb that will respond to the sender. The `Results.Extensions.TwiML` method creates a `TwiMLResult` that will serialize the `messagingResponse` to XML and set the correct content-type header.

If the sender has already been greeted, the bot responds with "YOU HAVE BEEN GREETED ALREADY!".

However, this last message is a placeholder. Replace the placeholder with the following code:

```csharp
var form = await request.ReadFormAsync().ConfigureAwait(false);
var body = form["Body"][0];

var representativeLookupClient = serviceProvider.GetRequiredService<RepresentativeLookupClient>();
try
{
    var representative = await representativeLookupClient.GetRepresentativeByAddress(body);
    messagingResponse.Message(
        $"Your representative is {representative.RepresentativeName} ({representative.Party})" +
        $", representing {representative.DistrictName}."
    );

    if (representative.PhotoUrl is not null)
    {
        messagingResponse.Append(new Message().Media(new Uri(representative.PhotoUrl)));
    }
}
catch (FailedToParseAddressException)
{
    messagingResponse.Message("The address you entered is invalid.");
}
catch (RepresentativeNotFoundException)
{
    messagingResponse.Message("Your representative could not be determined. " +
                              "This may be because there's no representative or multiple representatives for the given location. " +
                              "Try entering a more specific address.");
}
catch (Exception ex)
{
    logger.LogError(ex, "An unexpected error occurred when looking up representative");
    messagingResponse.Message("An unexpected error occurred.");
}
```

Once the sender has been greeted, the bot will assume that whatever message is sent next will be the address to return representative information for. The message that is sent will be stored in the form encoded "Body" parameter. 

After getting the body of the message, the bot will request an instance of `RepresentativeLookupClient` from the DI container, then the bot invokes the `RepresentativeLookupClient.GetRepresentativeByAddress` method passing in the body as a parameter.  
If no exception occurs, two messages will be added. One message will describe the district and representative, and the other will return the image of the elected official, if an image is provided.

A TwiML `Message` with `Media` will send the media as an MMS message, not as an SMS. You can choose to create a single message with the representative information and the image which looks great!  
However, I chose to create separate messages because of these reasons:

- An SMS is smaller and will be transmitted to the user faster, giving them the information as quickly as possible. 
- MMS uses mobile internet which may not be turned on, or there may be no mobile internet connectivity in rural areas.
- MMS may not be properly configured or disabled.

Your project is ready! Start your project using `dotnet run`.

To quickly test out your _/message_ endpoint, you can use the following PowerShell or Bash script in a new shell tab:

PowerShell:

```powershell
Invoke-WebRequest [YOUR_LOCALHOST_URL]/message `
    -Method Post `
    -Body @{ Body = 'Hi' } `
    -ContentType 'application/x-www-form-urlencoded' `
    -SessionVariable SmsBotSession

Invoke-WebRequest [YOUR_LOCALHOST_URL]/message `
    -Method Post `
    -Body @{ Body = '10602 Little Run Farm Ct, Vienna' } `
    -ContentType 'application/x-www-form-urlencoded' `
    -WebSession $SmsBotSession
```

Bash:

```bash
curl -X POST [YOUR_LOCALHOST_URL]/message \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "Body=Hi" \
    --cookie-jar SmsBotCookieJar

curl -X POST [YOUR_LOCALHOST_URL]/message \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "Body=10602 Little Run Farm Ct, Vienna" \
    --cookie SmsBotCookieJar

rm SmsBotCookieJar
```

Replace `[YOUR_LOCALHOST_URL]` with one of the localhost URLs printed out when running your .NET project.

The first web request will be responded to with the following TwiML:

```xml
<?xml version="1.0" encoding="utf-8"?>
<Response>
  <Message>Welcome to the U.S. representative lookup bot. Respond with your address.</Message>
</Response>
```

The subsequent request will be responded to with the following TwiML:

```xml
<?xml version="1.0" encoding="utf-8"?>
<Response>
  <Message>Your representative is Gerald E. "Gerry" Connolly (Democratic Party), representing Virginia's 11th congressional district.</Message>
  <Message>
    <Media>http://bioguide.congress.gov/bioguide/photo/C/C001078.jpg</Media>
  </Message>
</Response>
```

If everything is working as expected, you can now configure your Twilio Phone Number to use your Minimal API endpoint as the SMS webhook.

### Configure your Twilio SMS Webhook 

Start your project if it isn't running yet (`dotnet run`) and take note of one of the localhost URLs printed out by the application. For Twilio to be able to send HTTP requests to your local web server, the server needs to become publicly accessible. ngrok is a free secure tunneling service that can make your local web servers public.

Run the following ngrok command in a separate shell:

```bash
ngrok http [YOUR_ASPNET_URL]
```

Replace `[YOUR_ASPNET_URL]` with the localhost URL from your .NET application. If you're using an HTTPS localhost URL, you'll need to [authenticate ngrok](https://ngrok.com/docs/secure-tunnels#tunnel-authtokens).

The ngrok command will display an HTTPS Forwarding URL that makes your local web server public.

![ngrok http command output showing information about the secure tunnel, most importantly the public forwarding URLs](https://twilio-cms-prod.s3.amazonaws.com/images/Xcf4sbW_rAQ_fuqioIOcEj42MIkuRk20psfYf6nfesowBZ.width-500.png)

Now it's time to update your Twilio Phone Number to send HTTP requests to your _/message_ endpoint via the ngrok Forwarding URL. The URL should look something like **_https://1cc74f4c9f70.ngrok.io/message_**.

Go to [the Active Phone Numbers section in the Twilio Console](https://console.twilio.com/us1/develop/phone-numbers/manage/incoming?frameUrl=%2Fconsole%2Fphone-numbers%2Fincoming%3Fx-target-region%3Dus1) and click on your Twilio Phone Number.  
This will take you to the configuration for the phone number. Find the Messaging section and under the " **A MESSAGE COMES IN**" label, set the dropdown to Webhook. In the text field next to it, enter your ngrok forwarding URL with _/message_ appended to it. Select “ **HTTP POST** ” in the last dropdown. 

![Messaging section in the Twilio Phone Number configuration page. Under the "A MESSAGE COMES IN" label, a dropdown is set to "Webhook", the text field next to it is configured to "https://eaa3c4359db8.ngrok.io/message", and the dropdown next to that is set to "HTTP POST".](https://twilio-cms-prod.s3.amazonaws.com/images/JA3Ixn88UiVfm7Te23s7gRxbBo_44uK-SSAOph44EmKMcu.width-500.png)

Finally, click the **Save** button at the bottom of the page.

### Test your SMS Twilio Bot

To test your bot, you can either use a personal phone to send text messages, or you can [use the Twilio Dev Phone to test](https://www.twilio.com/blog/test-sms-and-phone-call-applications-with-twilio-dev-phone).

Send a text message to your Twilio Phone Number with anything as the body. You should receive the greeting you configured. Once you've been greeted, reply with any address that you want to retrieve representative and district information for.

![Conversation between an iPhone user and the SMS bot. The user says "Hi" and the bot responds with "Welcome to the U.S. Representative lookup bot. Respond with your address. The user sends an address and the bot responds with "Your representative is Gerald E. Connolly (Democratic Party) representing Virginia"s 11th congressional district. Then the bot sends an image of the representative.](https://twilio-cms-prod.s3.amazonaws.com/images/XUTXLzXFkI16xCVixHDGVCYGvEKh9oSGRwzjoNlEVwHCgK.width-500.png)

## Future improvements

This bot is a great start, however, you can improve this solution in a couple of ways:

You can respond to the user with more information about the elected official and how to reach them. And instead of only returning U.S. representatives, you could return elected officials from every level.

Currently, anyone can send HTTP requests to your webhook and pretend to be Twilio. This bot doesn't respond with any sensitive information so the risk is lower, however, it is always a good idea to [secure your webhooks using webhook signature validation](https://www.twilio.com/docs/usage/tutorials/how-to-secure-your-csharp-aspnet-core-app-by-validating-incoming-twilio-requests).

In this tutorial, you manually spun up a secure tunnel using ngrok, but you could also do this [programmatically so that a tunnel is created and your Twilio SMS webhook is updated whenever you start your .NET application](https://www.twilio.com/blog/integrate-ngrok-into-aspdotnet-core-startup-and-automatically-update-your-webhook-urls).

## Next steps

If you made it this far, a big shout-out to you! 🎉

In this tutorial, you learned how to respond to incoming text messages with Twilio, but if you’re looking to learn more, you can also [initiate text messages yourself using the Twilio SDK](https://www.twilio.com/blog/2016/04/send-an-sms-message-with-c-in-30-seconds.html), or [send emails using Twilio SendGrid](https://www.twilio.com/blog/send-emails-using-the-sendgrid-api-with-dotnetnet-6-and-csharp).

If you found this useful, let me know and share what you're working on. I can't wait to see what you build!

_Image credit: [Matthew Geason](https://www.linkedin.com/in/matthew-geason) has kindly allowed us to use his picture of the U.S. Capitol._