Hubspotワークフローでウェブフック利用時のリクエスト検証(Validating requests from HubSpot)

# Hubspotワークフローでウェブフック利用時のリクエスト検証方法

本記事ではC# (.NETCore)でのリクエスト検証サンプルについて記載します。

https://developers.hubspot.com/docs/faq/validating-requests-from-hubspot

実現する検証は上記のものになります。

## Hubspotワークフローのウェブフックについて

https://knowledge.hubspot.com/workflows/how-do-i-use-webhooks-with-hubspot-workflows

詳細は上記ドキュメント参照ですが、Hubspotのある操作をトリガにウェブフックリクエストを行うことが出来ます。
本記事執筆時点では、Enterprise版限定の機能ではあります...。

今回はここで「Use Request Signature」にチェックを入れた際に実施可能なバリデーションについて説明します。

### 事前準備

上記リンクの「Verify request signatures in workflow webhooks」記載の手順を踏む必要があります。

- developerアカウントのサインアップ
  - https://app.hubspot.com/signup-v2/developers/step/join-hubspot
- アプリケーションの作成
  - https://developers.hubspot.com/docs/faq/how-do-i-create-an-app-in-hubspot

### APP IDの設定

「Use Request Signature」にチェックを入れた際にAPP IDの指定が必要になります。
「アプリケーションの作成」で作成したアプリケーションのIDを指定します。(e.g. 123456)

## ウェブフックリクエスト先のアプリケーション

### ウェブフックリクエスト先のcontroller

```C#
        [HttpPost]
        public async Task<IActionResult> PostAsync(JObject jObject)
        {
            // リクエストがHubspotウェブフックによるものか検証
            if (!_hubSpotService.ValidRequestHeader(jObject.ToString(Formatting.None)))
            {
                return BadRequest();
            }
            return Jobject.ToString();
       }
```

HubspotからのウェブフックはPOST限定なので、POSTで受け付けます。

今回はrequest bodyの内容はJObjectで受け取ります。
リクエストボディの文字列がバリデーションに必要ですが、ToString(Formatting.None)で取得します。
フォーマットを指定しないと、余分な空白などが設定されて、SHA256ハッシュ値があわなくなりますのでご注意ください。

#### Startup.cs

今回はサービスクラスから、HttpContextにアクセスし、ヘッダを取得します。
services.AddHttpContextAccessor();を指定することにより可能になります。

```C#

public void ConfigureServices(IServiceCollection services)
        {
            // (略)
            services.AddScoped<IHubSpotService, HubSpotService>();
            services.AddHttpContextAccessor();
        }
```


#### HubSpotService.cs

Hubspotから行われるウェブフックリクエストに格納されているsignatureは全て小文字になります。
そのため、SHA256のハッシュ値をToLower()で小文字にしたものと比較します。

また、ヘッダのバージョンによってハッシュ値の生成が異なるので、実際はバージョンによって処理を分岐させたほうがよいでしょう。
本記事では、V2前提の記述となります。
（記事執筆時点では、V2のリクエストが投げられる)

https://developers.hubspot.com/docs/faq/validating-requests-from-hubspot


```C#

 public class HubSpotService : IHubSpotService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public HubSpotService(IHttpContextAccessor httpContextAccessor
        {
             _httpContextAccessor = httpContextAccessor;
        }

       public bool ValidRequestHeader(string requestBody)
        {

            if (!_httpContextAccessor.HttpContext.Request.Headers.TryGetValue("X-HubSpot-Signature", out StringValues signature))
            {
                throw new NotImplementedException();
            }

            if (!_httpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-HubSpot-Signature-Version"))
            {
                throw new NotImplementedException();
            }
            // hubspotアプリのclientsecretを指定。(e.g. 00000000-1111-2222-3333-444444444444)
            string secretId = "hubspot-client-secret";

            string method = "POST";

            // ワークフローで指定したウェブフックURLを指定
            string url = "https://hogehoge.com/webhook";

            byte[] bytes = Encoding.UTF8.GetBytes(secretId + method + url + requestBody);
            var crypto = new SHA256CryptoServiceProvider();
            byte[] hashValue = crypto.ComputeHash(bytes);
            crypto.Clear();

            var result = new StringBuilder();
            foreach (byte b in hashValue)
            {
                result.Append(b.ToString("X2"));
            }

            return signature.Equals(result.ToString().ToLower());
        }
```

今回はハッシュ値を取得する部分をベタ書きしていますが、関数化したりするなど実際は綺麗に書いたほうがよいでしょう。

V2の場合は「クライアントシークレット＋"POST"+ウェブフックURL＋リクエストボディ」をSHA256でハッシュ化した値と比較します。
ちなみにV1の場合はもう少しシンプルです。

## まとめ

- Hubspotからのウェブフックリクエストヘッダ(X-HubSpot-Signature)に入っているSHA256ハッシュ値は小文字
- JObjectで処理する場合は、Formatting.Noneを指定して文字列を取得する
- V2の場合は「クライアントシークレット＋"POST"+ウェブフックURL＋リクエストボディ」のハッシュ値と比較