パートナー センター WebhookPartner Center webhooks

適用対象Applies To

  • パートナー センターPartner Center
  • 21Vianet が運営するパートナー センターPartner Center operated by 21Vianet
  • Microsoft Cloud ドイツのパートナー センターPartner Center for Microsoft Cloud Germany
  • 米国政府機関向け Microsoft Cloud のパートナー センターPartner Center for Microsoft Cloud for US Government

パートナーセンターの Webhook Api を使用すると、パートナーはリソース変更イベントに登録できます。The Partner Center Webhook APIs allow partners to register for resource change events. これらのイベントは、パートナーの登録済み URL に HTTP 投稿の形式で配信されます。These events are delivered in the form of HTTP POSTs to the partner's registered URL. パートナーセンターからイベントを受信するために、パートナーはパートナーセンターがリソース変更イベントを投稿できるコールバックをホストします。To receive an event from Partner Center, partners will host a callback where Partner Center can POST the resource change event. イベントはデジタル署名されるので、パートナーはパートナーセンターから送信されたことを確認できます。The event will be digitally signed so that the partner can verify that it was sent from Partner Center.

パートナーは、次の例のように、パートナーセンターでサポートされている Webhook イベントから選択できます。Partners can select from Webhook events, like the following examples, that are supported by Partner Center.

  • テスト イベント ("test-created")Test Event ("test-created")

    このイベントを使用すると、テストイベントを要求し、その進行状況を追跡することによって、登録を自己オンボードしてテストできます。This event allows you to self-onboard and test your registration by requesting a test event and then tracking its progress. イベントを配信しようとしているときに、Microsoft から受信されているエラーメッセージを確認できます。You can see the failure messages that are being received from Microsoft while trying to deliver the event. この制限は、"テスト作成" イベントにのみ適用されます。This restriction only applies to "test-created" events. 7日を経過したデータは削除されます。Data older than seven days will be purged.

  • サブスクリプション更新イベント ("subscription-updated")Subscription Updated Event ("subscription-updated")

    このイベントは、サブスクリプションが変化したときに発生します。This event is raised when the subscription changes. これらのイベントは、パートナー センター API を通じて変更が行われた場合に加えて、内部変更が行われた場合にも生成されます。These events will be generated when there is an internal change in addition to when changes are made through the Partner Center API.

    注意

    サブスクリプションが変更されてからサブスクリプションの更新イベントがトリガーされるまでに、最大48時間の遅延が発生します。There is a delay of up to 48 hours between the time a subscription changes and when the Subscription Updated event is triggered.

  • しきい値超過イベント ("usagerecords-thresholdExceeded")Threshold Exceeded Event ("usagerecords-thresholdExceeded")

    いずれかの顧客による Microsoft Azure の使用量が、使用量の支出予算 (しきい値) を超えると、このイベントが発生します。This event is raised when the amount of Microsoft Azure usage for any customer exceeds their usage spending budget (their threshold). 詳細については、「顧客/パートナーの Azure 支出予算を設定する」を参照してください。または、お客様に対して設定します。For more information, see [Set an Azure spending budget for your customers/partner-center/set-an-azure-spending-budget-for-your-customers).

  • 紹介作成イベント ("紹介-作成済み")Referral Created Event ("referral-created")

    このイベントは、参照が作成されるときに発生します。This event is raised when the referral is created.

  • 紹介更新イベント ("紹介-更新")Referral Updated Event ("referral-updated")

    このイベントは、参照が更新されたときに発生します。This event is raised when the referral is updated.

  • 請求書の準備完了イベント ("請求書の準備完了")Invoice Ready Event ("invoice-ready")

    このイベントは、新しい請求書の準備が整ったときに発生します。This event is raised when the new invoice is ready.

将来の Webhook イベントは、パートナーが制御していないシステムで変更されるリソースに対して追加されます。また、これらのイベントを可能な限り "リアルタイム" の近くに取得するために更新が行われます。Future Webhook events will be added for resources that change in the system that the partner isn't in control of, and further updates will be made to get those events as close to "real time" as possible. イベントがビジネスに付加されるパートナーからのフィードバックは、追加する新しいイベントを決定する際に役立ちます。Feedback from Partners on which events add value to their business will be useful in determining what new events to add.

パートナーセンターでサポートされている Webhook イベントの完全な一覧については、「 パートナーセンターの webhook イベント」を参照してください。For a complete list of Webhook events supported by Partner Center, see Partner Center webhook events.

前提条件Prerequisites

  • パートナー センターの認証に関するページで説明している資格情報。Credentials as described in Partner Center authentication. このシナリオでは、スタンドアロンアプリとアプリ + ユーザー資格情報の両方を使用した認証がサポートされています。This scenario supports authentication with both standalone App and App+User credentials.

パートナーセンターからのイベントの受信Receiving events from Partner Center

パートナーセンターからイベントを受信するには、パブリックにアクセス可能なエンドポイントを公開する必要があります。To receive events from Partner Center, you must expose a publicly accessible endpoint. このエンドポイントは公開されているため、通信がパートナーセンターからのものであることを検証する必要があります。Because this endpoint is exposed, you must validate that the communication is from Partner Center. 受信したすべての Webhook イベントは、Microsoft ルートにチェーンされている証明書を使用してデジタル署名されます。All Webhook events that you receive are digitally signed with a certificate that chains to the Microsoft Root. イベントの署名に使用される証明書へのリンクも提供されます。A link to the certificate used to sign the event will also be provided. これにより、サービスを再デプロイまたは再構成しなくても証明書を更新できます。This will allow the certificate to be renewed without you having to redeploy or reconfigure your service. パートナーセンターでは、イベントの配信が10回試行されます。Partner Center will make 10 attempts to deliver the event. 10回試行してもイベントが配信されない場合は、オフラインキューに移動され、配信時にそれ以上の試行は行われません。If the event is still not delivered after 10 attempts, it will be moved into an offline queue and no further attempts will be made at delivery.

次のサンプルは、パートナーセンターから投稿されたイベントを示しています。The following sample shows an event posted from Partner Center.

POST /webhooks/callback
Content-Type: application/json
Authorization: Signature VOhcjRqA4f7u/4R29ohEzwRZibZdzfgG5/w4fHUnu8FHauBEVch8m2+5OgjLZRL33CIQpmqr2t0FsGF0UdmCR2OdY7rrAh/6QUW+u+jRUCV1s62M76jbVpTTGShmrANxnl8gz4LsbY260LAsDHufd6ab4oejerx1Ey9sFC+xwVTa+J4qGgeyIepeu4YCM0oB2RFS9rRB2F1s1OeAAPEhG7olp8B00Jss3PQrpLGOoAr5+fnQp8GOK8IdKF1/abUIyyvHxEjL76l7DVQN58pIJg4YC+pLs8pi6sTKvOdSVyCnjf+uYQWwmmWujSHfyU37j2Fzz16PJyWH41K8ZXJJkw==
X-MS-Certificate-Url: https://3psostorageacct.blob.core.windows.net/cert/pcnotifications-dispatch.microsoft.com.cer
X-MS-Signature-Algorithm: rsa-sha256
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 195

{
    "EventName": "test-created",
    "ResourceUri": "http://localhost:16722/v1/webhooks/registration/test",
    "ResourceName": "test",
    "AuditUri": null,
    "ResourceChangeUtcDate": "2017-11-16T16:19:06.3520276+00:00"
}

注意

Authorization ヘッダーには "Signature" のスキームがあります。The Authorization header has a scheme of "Signature". これは、コンテンツの base64 でエンコードされた署名です。This is a base64 encoded signature of the content.

コールバックを認証する方法How to authenticate the callback

パートナーセンターから受信したコールバックイベントを認証するには、次の手順を実行します。To authenticate the callback event received from Partner Center, follow these steps:

  1. 必要なヘッダーが存在することを確認します (承認、x.509-ms-証明書 url、x.509 アルゴリズム)。Verify the required headers are present (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).

  2. コンテンツに署名するために使用する証明書をダウンロードします (x-ms-certificate-url)。Download the certificate used to sign the content (x-ms-certificate-url).

  3. 証明書チェーンを確認します。Verify the Certificate Chain.

  4. 証明書の "組織" を確認します。Verify the "Organization" of the certificate.

  5. バッファーに UTF8 エンコードを使用してコンテンツを読み取ります。Read the content with UTF8 encoding into a buffer.

  6. RSA 暗号化プロバイダーを作成します。Create an RSA Crypto Provider.

  7. データが、指定されたハッシュアルゴリズム (SHA256 など) で署名されたものと一致していることを確認します。Verify the data matches what was signed with the specified hash algorithm (for example SHA256).

  8. 検証が成功した場合は、メッセージを処理します。If the verification succeeds, process the message.

注意

既定では、署名トークンは Authorization ヘッダーで送信されます。By default, the signature token will be sent in an Authorization header. 登録で Signaturetokentomssignatureheader を true に設定すると、代わりに、署名トークンが x.509 署名ヘッダーで送信されます。If you set SignatureTokenToMsSignatureHeader to true in your registration, the signature token will be sent in the x-ms-signature header instead.

イベントモデルEvent model

次の表では、パートナーセンターイベントのプロパティについて説明します。The following table describes the properties of a Partner Center event.

PropertiesProperties

名前Name 説明Description
EventNameEventName イベントの名前です。The name of the event. {Resource}-{action} の形式で。In the form {resource}-{action}. たとえば、"テスト作成" などです。For example, "test-created".
ResourceUriResourceUri 変更されたリソースの URI。The URI of the resource that changed.
ResourceNameResourceName 変更されたリソースの名前。The name of the resource that changed.
AuditUrlAuditUrl 省略可能。Optional. 監査レコードの URI。The URI of the Audit record.
ResourceChangeUtcDateResourceChangeUtcDate リソースの変更が発生した日時 (UTC 形式)。The date and time, in UTC format, when the resource change occurred.

サンプルSample

次のサンプルは、パートナーセンターイベントの構造を示しています。The following sample shows the structure of a Partner Center event.

{
    "EventName": "test-created",
    "ResourceUri": "http://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/c0bfd694-3075-4ec5-9a3c-733d3a890a1f",
    "ResourceName": "test",
    "AuditUri": null,
    "ResourceChangeUtcDate": "2017-11-16T16:19:06.3520276+00:00"
}

Webhook ApiWebhook APIs

認証Authentication

Webhook Api のすべての呼び出しは、Authorization ヘッダーのベアラートークンを使用して認証されます。All calls to the Webhook APIs are authenticated using the Bearer token in the Authorization Header. アクセスするアクセストークンを取得 https://api.partnercenter.microsoft.com します。Acquire an access token to access https://api.partnercenter.microsoft.com. このトークンは、パートナーセンター Api の残りの部分にアクセスするために使用されるトークンと同じです。This token is the same token that is used to access the rest of the Partner Center APIs.

イベントの一覧を取得するGet a list of events

Webhook Api で現在サポートされているイベントの一覧を返します。Returns a list of the events that are currently supported by the Webhook APIs.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration/events

要求の例Request example

GET /webhooks/v1/registration/events
content-type: application/json
authorization: Bearer eyJ0e.......
accept: */*
host: api.partnercenter.microsoft.com

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 183
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: c0bcf3a3-46e9-48fd-8e05-f674b8fd5d66
MS-RequestId: 79419bbb-06ee-48da-8221-e09480537dfc
X-Locale: en-US

[ "subscription-updated", "test-created", "usagerecords-thresholdExceeded" ]

登録してイベントを受信するRegister to receive events

指定されたイベントを受信するようにテナントを登録します。Registers a tenant to receive the specified events.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration

要求の例Request example

POST /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer eyJ0e.....
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 219

{
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": ["subscription-updated", "test-created"]
}

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 718f2336-8b56-4f42-93ac-54896047c59a
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2

{
    "SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": [ "subscription-updated", "test-created" ]
}

登録の表示View a registration

テナントの Webhook イベント登録を返します。Returns the Webhooks event registration for a tenant.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration

要求の例Request example

GET /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 341
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: c3b88ab0-b7bc-48d6-8c55-4ae6200f490a
MS-RequestId: ca30367d-4b24-4516-af08-74bba6dc6657
X-Locale: en-US

{
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": ["subscription-updated", "test-created"]
}

イベント登録を更新するUpdate an event registration

既存のイベント登録を更新します。Updates an existing event registration.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration

要求の例Request example

PUT /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOR...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 258

{
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": ["subscription-updated", "test-created"]
}

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 718f2336-8b56-4f42-93ac-54896047c59a
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2

{
    "SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": [ "subscription-updated", "test-created" ]
}

テストイベントを送信して登録を検証するSend a test event to validate your registration

Webhook 登録を検証するテストイベントを生成します。Generates a test event to validate the Webhooks registration. このテストは、パートナーセンターからイベントを受信できることを検証することを目的としています。This test is intended to validate that you can receive events from Partner Center. これらのイベントのデータは、最初のイベントが作成されてから7日後に削除されます。Data for these events will be deleted seven days after the initial event is created. 検証イベントを送信する前に、登録 API を使用して "テスト作成" イベントに登録する必要があります。You must be registered for the "test-created" event, using the registration API, before sending a validation event.

注意

検証イベントを投稿する場合、1分あたり2要求のスロットル制限があります。There is a throttle limit of 2 requests per minute when posting a validation event.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents

要求の例Request example

POST /webhooks/v1/registration/validationEvents
MS-CorrelationId: 3ef0202b-9d00-4f75-9cff-15420f7612b3
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length:

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 181
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 04af2aea-d413-42db-824e-f328001484d1
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

{ "correlationId": "04af2aea-d413-42db-824e-f328001484d1" }

イベントが配信されたことを確認するVerify that the event was delivered

検証イベントの現在の状態を返します。Returns the current state of the validation event. この確認は、イベント配信の問題のトラブルシューティングに役立ちます。This verification can be helpful for troubleshooting event delivery issues. 応答には、イベントを配信するために行われた各試行の結果が含まれます。The Response contains a result for each attempt that is made to deliver the event.

リソース URLResource URL

https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/{correlationId}

要求の例Request example

GET /webhooks/v1/registration/validationEvents/04af2aea-d413-42db-824e-f328001484d1
MS-CorrelationId: 3ef0202b-9d00-4f75-9cff-15420f7612b3
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate

応答の例Response example

HTTP/1.1 200
Status: 200
Content-Length: 469
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 497e0a23-9498-4d6c-bd6a-bc4d6d0054e7
MS-RequestId: 0843bdb2-113a-4926-a51c-284aa01d722e
X-Locale: en-US

{
    "correlationId": "04af2aea-d413-42db-824e-f328001484d1",
    "partnerId": "00234d9d-8c2d-4ff5-8c18-39f8afc6f7f3",
    "status": "completed",
    "callbackUrl": "{{YourCallbackUrl}}",
    "results": [{
        "responseCode": "OK",
        "responseMessage": "",
        "systemError": false,
        "dateTimeUtc": "2017-12-08T21:39:48.2386997"
    }]
}

署名の検証の例Example for Signature Validation

コールバックコントローラー署名のサンプル (ASP.NET)Sample Callback Controller signature (ASP.NET)

[AuthorizeSignature]
[Route("webhooks/callback")]
public IHttpActionResult Post(PartnerResourceChangeCallBack callback)

署名の検証Signature Validation

次の例では、Webhook イベントからコールバックを受信しているコントローラーに Authorization 属性を追加する方法を示します。The following example shows how to add an Authorization Attribute to the controller that is receiving callbacks from Webhook events.

namespace Webhooks.Security
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Http;
    using System.Web.Http.Controllers;
    using Microsoft.Partner.Logging;

    /// <summary>
    /// Signature based Authorization
    /// </summary>
    public class AuthorizeSignatureAttribute : AuthorizeAttribute
    {
        private const string MsSignatureHeader = "x-ms-signature";
        private const string CertificateUrlHeader = "x-ms-certificate-url";
        private const string SignatureAlgorithmHeader = "x-ms-signature-algorithm";
        private const string MicrosoftCorporationIssuer = "O=Microsoft Corporation";
        private const string SignatureScheme = "Signature";

        /// <inheritdoc/>
        public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            ValidateAuthorizationHeaders(actionContext.Request);

            await VerifySignature(actionContext.Request);
        }

        private static async Task<string> GetContentAsync(HttpRequestMessage request)
        {
            // By default the stream can only be read once and we need to read it here so that we can hash the body to validate the signature from microsoft.
            // Load into a buffer, so that the stream can be accessed here and in the api when it binds the content to the expected model type.
            await request.Content.LoadIntoBufferAsync();

            var s = await request.Content.ReadAsStreamAsync();
            var reader = new StreamReader(s);
            var body = await reader.ReadToEndAsync();

            // set the stream position back to the beginning
            if (s.CanSeek)
            {
                s.Seek(0, SeekOrigin.Begin);
            }

            return body;
        }

        private static void ValidateAuthorizationHeaders(HttpRequestMessage request)
        {
            var authHeader = request.Headers.Authorization;
            if (string.IsNullOrWhiteSpace(authHeader?.Parameter) && string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, MsSignatureHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Authorization header missing."));
            }

            var signatureHeaderValue = GetHeaderValue(request.Headers, MsSignatureHeader);
            if (authHeader != null
                && !string.Equals(authHeader.Scheme, SignatureScheme, StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrWhiteSpace(signatureHeaderValue)
                && !signatureHeaderValue.StartsWith(SignatureScheme, StringComparison.OrdinalIgnoreCase))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Authorization scheme needs to be '{SignatureScheme}'."));
            }

            if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, CertificateUrlHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {CertificateUrlHeader} missing."));
            }

            if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, SignatureAlgorithmHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {SignatureAlgorithmHeader} missing."));
            }
        }

        private static string GetHeaderValue(HttpHeaders headers, string key)
        {
            headers.TryGetValues(key, out var headerValues);

            return headerValues?.FirstOrDefault();
        }

        private static async Task VerifySignature(HttpRequestMessage request)
        {
            // Get signature value from either authorization header or x-ms-signature header.
            var base64Signature = request.Headers.Authorization?.Parameter ?? GetHeaderValue(request.Headers, MsSignatureHeader).Split(' ')[1];
            var signatureAlgorithm = GetHeaderValue(request.Headers, SignatureAlgorithmHeader);
            var certificateUrl = GetHeaderValue(request.Headers, CertificateUrlHeader);
            var certificate = await GetCertificate(certificateUrl);
            var content = await GetContentAsync(request);
            var alg = signatureAlgorithm.Split('-'); // for example RSA-SHA1
            var isValid = false;

            var logger = GetLoggerIfAvailable(request);

            // Validate the certificate
            VerifyCertificate(certificate, request, logger);

            if (alg.Length == 2 && alg[0].Equals("RSA", StringComparison.OrdinalIgnoreCase))
            {
                var signature = Convert.FromBase64String(base64Signature);
                var csp = (RSACryptoServiceProvider)certificate.PublicKey.Key;

                var encoding = new UTF8Encoding();
                var data = encoding.GetBytes(content);

                var hashAlgorithm = alg[1].ToUpper();

                isValid = csp.VerifyData(data, CryptoConfig.MapNameToOID(hashAlgorithm), signature);
            }

            if (!isValid)
            {
                // log that we were not able to validate the signature
                logger?.TrackTrace(
                    "Failed to validate signature for webhook callback",
                    new Dictionary<string, string> { { "base64Signature", base64Signature }, { "certificateUrl", certificateUrl }, { "signatureAlgorithm", signatureAlgorithm }, { "content", content } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Signature verification failed"));
            }
        }

        private static ILogger GetLoggerIfAvailable(HttpRequestMessage request)
        {
            return request.GetDependencyScope().GetService(typeof(ILogger)) as ILogger;
        }

        private static async Task<X509Certificate2> GetCertificate(string certificateUrl)
        {
            byte[] certBytes;
            using (var webClient = new WebClient())
            {
                certBytes = await webClient.DownloadDataTaskAsync(certificateUrl);
            }

            return new X509Certificate2(certBytes);
        }

        private static void VerifyCertificate(X509Certificate2 certificate, HttpRequestMessage request, ILogger logger)
        {
            if (!certificate.Verify())
            {
                logger?.TrackTrace("Failed to verify certificate for webhook callback.", new Dictionary<string, string> { { "Subject", certificate.Subject }, { "Issuer", certificate.Issuer } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Certificate verification failed."));
            }

            if (!certificate.Issuer.Contains(MicrosoftCorporationIssuer))
            {
                logger?.TrackTrace($"Certificate not issued by {MicrosoftCorporationIssuer}.", new Dictionary<string, string> { { "Issuer", certificate.Issuer } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Certificate not issued by {MicrosoftCorporationIssuer}."));
            }
        }
    }
}