Azure Custom EventGrid Event Handler with WebHooks

Setting up a custom Azure EventGrid Event Handler can be done through the use of a WebHook Endpoint when defining the Subscription, enabling our local apps to react in response to EventGrid Events, whether the Events originated from resources in Azure or from completely external services by ourselves or other service providers.

In this blog, we take a quick exploration into a standard workflow of how to make a local/on-prem ASP.NET Web API become a handler of Events, by Subscribing to an Event Grid Topic. We will send an Event to an EventGrid Topic through a separate console app of our own, then we will prepare the ASP.NET Web API to listen for the Events with code and by subscribing to the Topic.

Some Pre-requisites:

  • Ngrok - get this here (https://ngrok.com/download)
  • We will assume you are on your personal machine, but this will work on your corporate on-premises infrastructure where you are permitted and unrestricted to use Ngrok
  • We will be using Windows

Create Event Topic

First, create an Event Grid Topic Resource in Azure. Proceed through the wizard and Create:

Create an EventGrid Topic

After the creation of the Topic, observe the Topic Endpoint URL and also the Access Keys under the Access Keys Menu. We will need these shortly.

Now in a new Console App, we will use the following code to send our own Events into the Topic so that any other service can subscribe to it and automatically receive Events:

public class Program
{
    public static async Task Main(string[] args)
    {
        //use your topic endpoint and access key here
        string endpoint = "[YOUR_TOPIC_ENDPOINTURL]";
        string key = "[ONE_OF_YOUR_TOPIC_ACCESS_KEYS]";
        string topichostname = new Uri(endpoint).Host;
        
        //you can customise the Event details
        EventGridEvent treeEvent = new EventGridEvent(
        id:Guid.NewGuid().ToString(),
        subject: "Tree Planted third tree",
        data: new { Message = "seed in the soil"},
        eventType: "TreePlanted",
        eventTime: DateTime.UtcNow,
        dataVersion:"1.0"
        );

        TopicCredentials credentials = new TopicCredentials(key);

        EventGridClient client = new EventGridClient(credentials);

        await client.PublishEventsAsync(topichostname, new EventGridEvent[] { treeEvent });
    }
}

Console App code to send Events to a known Topic

Great, next is creating an Web API to ultimately consume the events.

Create Azure Event Consumer

Now create a separate ASP.NET Web API to run locally that will act as the Event Subscriber or Consumer and do something with the Event:

[ApiController]
[Route("[controller]")]
public class ReceiveController : ControllerBase
{
    //a one-time header value used by Azure when 
    //validating an Endpoint for Webhook
    private bool EventTypeSubcriptionValidation 
    => HttpContext.Request.Headers["aeg-event-type"].FirstOrDefault() == 
    "SubscriptionValidation";
     
    // the standard header value used for event notifications 
    private bool EventTypeNotification
    => HttpContext.Request.Headers["aeg-event-type"].FirstOrDefault() ==
    "Notification";

    private readonly ILogger<ReceiveController> _logger;

    public ReceiveController(ILogger<ReceiveController> logger)
    {
    	_logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> Post()
    {
        using (var requestStream = new StreamReader(Request.Body))
        {
            //read http request body and deserialise
            var bodyJson = await requestStream.ReadToEndAsync();

            var events = JsonConvert.DeserializeObject<List<EventGridEvent>>(bodyJson);

            if (EventTypeSubcriptionValidation)
            {   
                //first validation request from Azure comes here
                var subValidationEventData =
                ((JObject)events.First().Data).ToObject<SubscriptionValidationEventData>();
                //this one-time validationCode is expected back by Azure on 
                //the first ever validation request
                return new OkObjectResult(
                new SubscriptionValidationResponse(subValidationEventData.ValidationCode)
                );
            }
            else if (EventTypeNotification)
            {
                var notificationEvent = events.First();
                //arbitrary logic, do something with the event properties
                _logger.LogInformation(notificationEvent.Subject);

                return new OkResult();
       		}
    	}

    return new BadRequestResult();
    }
}

A web API Endpoint that will deserialise the HTTP Request stream into interpretable EventGrid objects

This Web API code on its own like this is not quite ready to start getting Events just yet. So now the question is, how can this local Web API receive Events from Azure? Enter Ngrok..

Ngrok Setup

In short, NGrok in this case will enable us to forward HTTP requests from Azure Event Grid Topic to our local machine on a particular port.

Download Ngrok from here (https://ngrok.com/download), install and run the .exe file. We will now need to enter the startup command to set the particular port on our machine that our ngrok session should send requests to. In our case we want this port to be the port that our Web API runs on, in HTTP mode. You can see this port number by opening the launchSettings.json in your Web API Project's Properties Folder on the http object's applicationurl, or by simply starting the Web API project under the http profile and observing the port number used. In my case this was 5233:

Example of local web api address running with swagger scaffolding

Now run the Ngrok exe as Administrator and enter:

ngrok.exe http [your_port_number]

You can see that an ngrok tunnel has been made for your local machine:

Keep this session window open and running. Note the address shown on your machine on the 'Forwarding' line, we will need this shortly but it now means that http requests that are sent to the Ngrok address will be sent to whatever application is running on the localhost port, root to root.

💡
Remember, if you are using a corporate/work machine, be sure to have full authorisation from your Network Administrator when using Ngrok!! Just saying 🧐

Set Subscription on Topic in Azure

Now go back into Azure and onto the EventGrid Topic's Overview Page, then Event Subscription. When you fill in the following details, DON'T click Create just yet

Set a name, set the Event Schema as Event Grid Schema as this is the same schema being used by our Event producer - the console App.

Set the Endpoint Type to Webhook, and then add your ngrok endpoint from earlier plus your controller's specific named endpoint method. Mine is /[controllerName].

Mine is set as [ngrok-endpoint-url]/[controllerName] but DON'T click Create yet. Let's take a preview below:

Set up a subscription with a WebHook to your NGrok endpoint on your Web API controller Name as the endpoint in Azure

The reason we take a pause here is because we have to make sure the Web API is running first because during the Creation of a Subscription when we are using a Web Hook endpoint, Azure will attempt to make sure the endpoint is valid first. So make sure the Web API project is running in http mode as mentioned before.

Upon clicking Create for the subscription, Azure will send a validation request to the set ngrok endpoint with a specific header with the name "SubscriptionValidation" and expects a specific validation code in the request payload to be returned back from our application. This is the reason why we have a specific section that handles a validation request in our Web API code.

Now Click Create to create the Event Subscription. This should succeed, and you should also see at least one 200 OK response on the Ngrok window. Subsequent requests will be shown here too:

The Ngrok window when receiving requests

Sending and Receiving Azure Events

With Ngrok and the Web API still running, now run the Console App we made earlier. The console will automatically send an Event to the EventGrid Topic

You will now start seeing the single EventGrid Event from the Console being consumed by the local Web API that is running:

As you receive Events, your Web API should execute its logic

Your local Web API can now react to Events from EventGrid as if it were a native Azure Resource itself!! Incredible.

Conclusions

This blog post illustrates the idea of a local app being able to react to Azure EventGrid Events just like any other Azure resource

Also, if you wish to maintain the URL that you get for Ngrok, you can sign up for an account and set your Auth Key and get longer sessions or configure a static domain instead (More here https://ngrok.com/blog-post/free-static-domains-ngrok-users) from your Ngrok account Dashboard.