azure-functionsazure-eventgridcloudevents

Azure Event Grid Subscription WebHook Validation Fails with Cloud Events Schema v1.0


When attempting to create a WebHook Subscription, the deployment fails to validate an HTTP Trigger within Azure Functions for a Cloud Events v1.0 Schema.I have confirmed my endpoint handles the validation handshake with the appropriate response. My Event Grid Domain is using Cloud Events v1.0 Schema. It appears my Azure function is not even being called during validation because there is no output in the console of the function.

Should this work for Cloud Events v1.0 and WebHooks?

Deployment has failed with the following error: {"code":"Url validation","message":"The attempt to validate the provided endpoint xxxxx failed."}

Here is my code :

[FunctionName("ProcessEvent")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestMessage req, ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var requestmessage = await req.Content.ReadAsStringAsync();
            var message = JToken.Parse(requestmessage);

            if (message.Type == JTokenType.Array)
            {
                // If the request is for subscription validation, send back the validation code.
                if (string.Equals((string)message[0]["eventType"],
                "Microsoft.EventGrid.SubscriptionValidationEvent",
                System.StringComparison.OrdinalIgnoreCase))
                {
                    log.LogInformation("Validate request received");

                    var myObj = new { validationResponse = message[0]["data"]["validationCode"].ToString() };
                    var jsonToReturn = JsonConvert.SerializeObject(myObj);

                    return new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new StringContent(jsonToReturn, Encoding.UTF8, "application/json")
                    };
                }
            }
            else
            {
                // The request is not for subscription validation, so it's for an event.
                // CloudEvents schema delivers one event at a time.
                log.LogInformation($"Source: {message["source"]}");
                log.LogInformation($"Time: {message["eventTime"]}");
                log.LogInformation($"Event data: {message["data"].ToString()}");
            }

            return req.CreateResponse(HttpStatusCode.OK);
}

Solution

  • The endpoint validation with CloudEvents v1.0 is different from the delivery schema such as EventGridSchema and CustomInputSchema described here.

    For test purposes, you can use a Webhook.site for your endpoint handler. The following screen shows sending an OPTIONS call by the EventGrid for its validation handshake:

    enter image description here

    Note, that the address for the endpoint handler must be https. As you can see the above picture, there is a webhook-request-callback header. Copy its value a and put it on the browser and send it. You should get the following text:

    "Webhook succesfully validated as a subscription endpoint."
    

    From now, the webhook can received a Notification messages, see the following screen:

    enter image description here

    As you can see, the CloudEvent v1.0 requires to implement the HTTP OPTIONS response for the endpoint validation. The following is an example of this implementation:

    string webhookcallback = req.Headers.GetValues("WebHook-Request-Callback")?.FirstOrDefault()?.Trim();       
    if(string.IsNullOrEmpty(webhookcallback) == false)
    {
        var hrm2 = req.CreateResponse(HttpStatusCode.OK);
        hrm2.Headers.Add("WebHook-Request-Origin", "eventgrid.azure.net");
        hrm2.Headers.Add("WebHook-Allowed-Rate", "120");
    
        System.Threading.ThreadPool.QueueUserWorkItem(delegate (object state)
        {
            Task.Delay(5000).Wait();
            using(var client = new HttpClient())
            {
                log.Warning($"{client.GetAsync(webhookcallback).Result.Content.ReadAsStringAsync().Result}");
            }
        });
    
        return hrm2;
    }
    

    Place the above code snippet into your function. Note, that the calling the WebHook-Request-Callback address can not be within the OPTIONS call, it must be done after its OK response back to the EventGrid, therefore we have some delay and background thread. It will be nice, if the EventGrid will accept this call within the OPTIONS call.