Under the hood of ASP.NET Core WebHooks - Verification Requests

This is third part of my Under the hood of ASP.NET Core WebHooks series:

I was expecting this post subject (verification requests) to be plain and simple, yet it managed to surprise me.

What's verification request about

Some webhooks perform a verification request as part of creating a subscription. This request is typically a GET (while notifications are POSTs) with a dedicated query parameter which serves as challenge. The goal is to verify that the provided URL in fact supports the webhook. To confirm that, the application is expected to echo the challenge parameter value in response body. Dropbox webhook is textbook example of this flow.

What's in the box for verification request

ASP.NET Core WebHooks provides support for verification requests through WebHookGetHeadRequestFilter. The filter is "triggered" when IWebHookGetHeadRequestMetadata interface is implemented by metadata. I wanted to see if it provides what I needed for my WebSub compliant receiver so I've added it to metadata, which enforced implementation of three properties.

public class WebSubMetadata : WebHookMetadata, IWebHookGetHeadRequestMetadata, ...
{
    ...

    public bool AllowHeadRequests => false;

    public string ChallengeQueryParameterName => "hub.challenge";

    public int SecretKeyMinLength => 0;
}

The AllowHeadRequests and ChallengeQueryParameterName are self-explanatory. As I didn't care about any keys at this stage I've decided to set SecretKeyMinLength to zero. I've run the demo application, fired the prepared request from Postman and (to my surprise) received a 500 response. I've quickly looked through the logs and found this:

System.InvalidOperationException: Could not find a valid configuration for the 'websub' WebHook receiver. Configure secret keys for this receiver.

But I didn't want any secret keys...

Further examination of WebHookGetHeadRequestFilter revealed that responding to verification requests isn't its only responsibility. It also confirms that secret key is properly configured for given receiver and not having one is not an option. This is surprising as secret keys are not related to verification requests but signature verification (hopefully the subject of next post in this series). Regardless, I had to configure one.

The WebHookGetHeadRequestFilter is looking for secret key under WebHooks:{webHookReceiver}:SecretKey:{id} configuration key. If the webhook URL doesn't contain id, the value default will be used in its place. This means that scenarios where all webhooks URLs are not known upfront is becoming challenging and requires smart usage of in-memory configuration provider or implementation of custom configuration provider.

For testing purposes I've put a dummy value into configuration file and received the response I wanted.

What if that's not what you need

The secret key requirement is a pain, but for most cases it's bearable. What if you have special requirements (which ended up being the case for my WebSub receiver)? The WebHookGetHeadRequestFilter doesn't provide way for customization, so the only solution seems to be using IWebHookFilterMetadata (which was briefly described in the introduction post) and creating your own filter. The ASP.NET Core WebHooks helps a little bit here by exposing static Order property on every filter, which allows placing the equivalent in the right spot. The filter should implement IResourceFilter or IAsyncResourceFilter.

public class WebSubWebHookIntentVerificationFilter : IAsyncResourceFilter, IOrderedFilter
{
    ...

    public int Order => WebHookGetHeadRequestFilter.Order;

    ...
}

This gives full freedom in handling verification requests.