Block Spam Calls and Robocalls with C#

Spam, scams, and robocalls are at best annoying. For high-volume customer service centers, they can significantly impact the bottom line. Let’s leverage the Twilio Marketplace and our C# skills to block spam callers, robocallers, and scammers.

Before getting started, you should already know how to handle incoming calls with Twilio.

Get a Spam Filtering Add-on

The Twilio Add-ons Marketplace is a great place to find Add-ons for your Twilio apps. You can integrate third-party technologies without leaving the comfort of the Twilio API. You can access the Add-ons from your Twilio Console. Today, we’re going to look at a few Voice Add-ons that can help us with this spam problem. They are, in no particular order, Whitepages Pro Phone Reputation, Marchex Clean Call, and Nomorobo Spam Score:

Voice Spam Add-ons

We’ll be writing code to work with all three of these Add-ons, but you can research which one you think will work best for your requirements. Also, keep an eye on the Marketplace, as new Add-ons are always showing up.

Installing the Add-on

Once you’ve decided on the Add-on you’d like to use, click the Install button and agree to the terms. In our use case today, we want to make use of these Add-ons while handling incoming voice calls, so make sure the “Incoming Voice Call” box for “Use In” is checked and click “Save” to save any changes:

Use in incoming voice call

Note the “Unique Name” setting. You need to use this in the code that you will write to read the Add-on’s results. In the code for this guide, we are sticking with the default names.

Check Phone Number Score in C#

When Twilio receives a phone call for your phone number, it will send details of the call to your webhook (more on how to configure that later). In your webhook code, you create a TwiML response to tell Twilio how to handle the call.

For spam-checking, our code needs to check the spam score of the number and deal with the call differently depending on whether the Add-on considers the caller to be a spammer or not. In our example code here, we’ll return a <Reject> TwiML tag to send spammers packing and a <Say> TwiML tag to welcome legit callers.

The code for our controller is part of a standard ASP.NET MVC project:

Loading Code Samples...
Language
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
Block Spam Calls with C#

Notice the code has checks for all three of the Add-ons we mentioned before. The code is written to be very flexible and handle missing data in the JSON response, so feel free to copy and paste even if you only plan to use one of the Add-ons.

How to Check Whitepages Pro Phone Reputation

Here’s an example of what Whitepages Pro Phone Reputation will return in the “AddOns” form parameter:

Loading Code Samples...
Language
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "whitepages_pro_phone_rep": {
      "code": null,
      "message": null,
      "request_sid": "XR63dbb880c4fcd054a810f603f304bd31",
      "result": {
        "error": null,
        "id": "Phone.4d796fef-a2df-4b08-cfe3-bc7128b6f6bb.Durable",
        "phone_number": "2069735100",
        "report_count": 1,
        "reputation_details": {
          "category": null,
          "score": 0,
          "type": "NotSpamType"
        },
        "reputation_level": 1,
        "volume_score": 1,
        "warnings": []
      },
      "status": "successful"
    }
  }
}
White Pages Pro Reputation Add-on JSON Response

Parsing that data in C# is done as follows:

Loading Code Samples...
Language
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
Block Spam Calls with C#

How to Check Marchex Clean Call

Here’s an example of what Marchex Clean Call will return:

Loading Code Samples...
Language
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "marchex_cleancall": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "result": {
          "recommendation": "PASS",
          "reason": "CleanCall"
        }
      }
    }
  }
}
Marchex Clean Call Add-on JSON result

Working with that data in C# can be handled with:

Loading Code Samples...
Language
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
Block Spam Calls with C#

How to Check Nomorobo Spam Score

Here’s an example of what Nomorobo Spam Score will return:

Loading Code Samples...
Language
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "nomorobo_spamscore": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "status": "success",
        "message": "success",
        "score": 0
      }
    }
  }
}
Nomorobo Add-on JSON result

Finally, handling that data in C#:

Loading Code Samples...
Language
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
Block Spam Calls with C#

Other Rejection Options

Using <Reject> is the simplest way to turn away spammers. However, you may want to handle them differently. The whole universe of TwiML is open to you. For example, you might want to record the call, have the recording transcribed using another Add-on, and log the transcription somewhere for someone to review.

Other Call Accept Options

For this example, we’re just greeting the caller. In a real-world scenario, you would likely want to connect the call using <Dial> (to call another number or Twilio Client), <Enqueue> the call to be handled by TaskRouter, or build an IVR using <Gather>.

Configure Phone Number Webhook

Now we need to configure our Twilio phone number to call our VoiceController whenever a call comes in. The URL for our controller is /voice, so we just need a public host for our ASP.NET application. You can publish your app to Azure or use ngrok to test locally.

Armed with the URL to the VoiceController, open the Twilio Console and find the phone number you want to use (or buy a new number). On the configuration page for the number, scroll down to "Voice" and next to "A CALL COMES IN," select "Webhook" and paste in the function URL. (Be sure "HTTP POST" is selected, as well.)

Voice Webhook Configuration

Testing a Blocked Call

You can quickly call your Twilio number to make sure your call goes through. However, how can we test a blocked spam result? The easiest way is to write some unit tests that pass some dummied up JSON to our controller action. For example, if we wanted to test a Nomorobo “BLOCK” recommendation, we could use the following JSON:

Loading Code Samples...
Language
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "nomorobo_spamscore": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "status": "success",
        "message": "success",
        "score": 1
      }
    }
  }
}
Nomorobo Add-on JSON result (blocked)

With that as our test fixture, we can write a test like the following to ensure that our call is blocked when we see the right data in the AddOns JSON:

Loading Code Samples...
Language
[TestMethod]
public void TestBlockedWithNomoRobo()
{
    var addOns = File.ReadAllText("spam_nomorobo.json");
    var controller = new VoiceController();
    var result = controller.Index(new VoiceRequest(), addOns);
    Assert.IsTrue(result.Data.ToString().Contains("<Reject"));
}
Nomorobo C# Unit Test

For a full solution with all the tests for each of the Add-on providers, see our GitHub repository.

What’s Next?

As you can see, the Twilio Add-ons Marketplace gives you a lot of options for extending your Twilio apps. Next, you might want to dig into the Add-ons reference or perhaps glean some pearls from our other C# tutorials. Wherever you’re headed next, you can confidently put spammers in your rearview mirror.

David Prothero
Hector Ortega
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "whitepages_pro_phone_rep": {
      "code": null,
      "message": null,
      "request_sid": "XR63dbb880c4fcd054a810f603f304bd31",
      "result": {
        "error": null,
        "id": "Phone.4d796fef-a2df-4b08-cfe3-bc7128b6f6bb.Durable",
        "phone_number": "2069735100",
        "report_count": 1,
        "reputation_details": {
          "category": null,
          "score": 0,
          "type": "NotSpamType"
        },
        "reputation_level": 1,
        "volume_score": 1,
        "warnings": []
      },
      "status": "successful"
    }
  }
}
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "marchex_cleancall": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "result": {
          "recommendation": "PASS",
          "reason": "CleanCall"
        }
      }
    }
  }
}
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "nomorobo_spamscore": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "status": "success",
        "message": "success",
        "score": 0
      }
    }
  }
}
using System.Diagnostics;
using System.Web.Mvc;
using Newtonsoft.Json.Linq;
using Twilio.AspNet.Common;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace BlockSpamCalls.Controllers
{
    public class VoiceController : TwilioController
    {
        private const int WhitepagesBadReputation = 4;
        private const int NomoroboSpamScore = 1;
        private const string SuccessfulStatus = "successful";

        [HttpPost]
        public TwiMLResult Index(VoiceRequest request, string addOns)
        {
            var response = new VoiceResponse();
            var isCallBlocked = false;

            if (!string.IsNullOrWhiteSpace(addOns))
            {
                Trace.WriteLine(addOns);

                var addOnData = JObject.Parse(addOns);
                if (addOnData["status"]?.ToString() == "successful")
                {
                    isCallBlocked = IsBlockedByNomorobo(addOnData["results"]?["nomorobo_spamscore"])
                                 || IsBlockedByWhitepages(addOnData["results"]?["whitepages_pro_phone_rep"])
                                 || IsBlockedByMarchex(addOnData["results"]?["marchex_cleancall"]);
                }
            }

            if (isCallBlocked)
            {
                response.Reject();
            }
            else
            {
                response.Say("Welcome to the jungle.");
                response.Hangup();
            }
            return TwiML(response);
        }

        private static bool IsBlockedByNomorobo(JToken nomorobo)
        {
            if (nomorobo?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var score = nomorobo["result"]?["score"]?.Value<int>();
            return score == NomoroboSpamScore;
        }

        private static bool IsBlockedByWhitepages(JToken whitepages)
        {
            if (whitepages?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var reputationLevel = whitepages["result"]?["reputation_level"].Value<int>();
            return reputationLevel == WhitepagesBadReputation;
        }

        private static bool IsBlockedByMarchex(JToken marchex)
        {
            if (marchex?["status"]?.Value<string>() != SuccessfulStatus) return false;

            var recommendation = marchex["result"]?["result"]?["recommendation"]?.Value<string>();
            return recommendation == "BLOCK";
        }
    }
}
{
  "status": "successful",
  "message": null,
  "code": null,
  "results": {
    "nomorobo_spamscore": {
      "request_sid": "XRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "status": "successful",
      "message": null,
      "code": null,
      "result": {
        "status": "success",
        "message": "success",
        "score": 1
      }
    }
  }
}
[TestMethod]
public void TestBlockedWithNomoRobo()
{
    var addOns = File.ReadAllText("spam_nomorobo.json");
    var controller = new VoiceController();
    var result = controller.Index(new VoiceRequest(), addOns);
    Assert.IsTrue(result.Data.ToString().Contains("<Reject"));
}