Partilhar via


Webhooks do Centro de Parceiros

Aplica-se a: Partner Center | Partner Center operado pela 21Vianet | Partner Center for Microsoft Cloud for US Government

Funções apropriadas: Administrador global | Administrador de Faturação | Agente administrativo | Agente de vendas | Agente do serviço de assistência

As APIs Webhook do Partner Center permitem que os parceiros se registrem para eventos de alteração de recursos. Esses eventos são entregues na forma de HTTP POSTs para a URL registrada do parceiro. Para receber um evento do Partner Center, os parceiros darão um retorno de chamada onde o Partner Center poderá POSTAR o evento de alteração de recursos. O evento será assinado digitalmente para que o parceiro possa verificar se foi enviado do Partner Center. As notificações Webhook são acionadas apenas para o ambiente que tem a configuração mais recente para Co-sell.

Os parceiros podem selecionar entre os eventos Webhook, como os exemplos a seguir, que são suportados pelo Partner Center.

  • Azure Fraud Event Detected ("azure-fraud-event-detected")

    Esse evento é gerado quando o evento de fraude do Azure é detetado.

  • Evento Aprovado de Relacionamento de Administrador Delegado ("dap-admin-relationship-approved")

    Esse evento é gerado quando os Privilégios de Administrador Delegado foram aprovados pelo locatário do cliente.

  • Evento de Relacionamento de Revendedor Aceito pelo Cliente ("Relacionamento de Revendedor-Aceito pelo Cliente")

    Esse evento é gerado quando o Relacionamento com o Revendedor é aprovado pelo locatário do cliente.

  • Evento Encerrado de Relacionamento de Administrador Delegado ("dap-admin-relationship-terminated")

    Esse evento é gerado quando os Privilégios de Administrador Delegado foram encerrados pelo cliente.

  • Relação de administrador do Dap encerrada por evento da Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    Esse evento é gerado quando a Microsoft encerra o DAP entre o locatário do Parceiro e do Cliente quando o DAP está inativo por mais de 90 dias.

  • Evento Granular Admin Access Assignment Activated ("granular-admin-access-assignment-activated")

    Esse evento é gerado quando a atribuição de acesso de Privilégios de Administrador Delegado Granular é ativada pelo parceiro depois que as funções do Microsoft Entra são atribuídas a grupos de segurança específicos.

  • Evento criado da atribuição de acesso de administrador granular ("granular-admin-access-assignment-created")

    Esse evento é gerado quando a atribuição de acesso Privilégios de Administrador Delegado Granular é criada pelo parceiro. Os parceiros podem atribuir funções do Microsoft Entra aprovadas pelo cliente a grupos de segurança específicos.

  • Evento Granular Admin Access Assignment Deleted ("granular-admin-access-assignment-deleted")

    Esse evento é gerado quando a atribuição de acesso de Privilégios de Administrador Delegado Granular é excluída pelo parceiro.

  • Evento Granular Admin Access Assignment Updated ("granular-admin-access-assignment-updated")

    Esse evento é gerado quando a atribuição de acesso de Privilégios de Administrador Delegado Granular é atualizada pelo parceiro.

  • Evento Granular Admin Relationship Activated ("granular-admin-relationship-activated")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular são criados e ativos para o cliente aprovar.

  • Evento Granular Admin Relationship Approved ("granular-admin-relationship-approved")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular foram aprovados pelo locatário do cliente.

  • Evento Granular Admin Relationship Expired ("granular-admin-relationship-expired")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular expiram.

  • Evento Granular Admin Relationship Updated ("granular-admin-relationship-updated")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular são atualizados pelo locatário do parceiro/cliente.

  • Evento Estendido Automático de Relacionamento de Administrador Granular ("granular-admin-relationship-auto-extended")

    Esse evento é gerado quando os privilégios de administrador delegado granular são estendidos automaticamente pelo sistema.

  • Evento Granular Admin Relationship Terminated ("granular-admin-relationship-terminated")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular são encerrados pelo locatário do parceiro/cliente.

  • Nova migração de comércio concluída ("new-commerce-migration-completed")

    Esse evento é gerado quando a nova migração de comércio é concluída.

  • Nova migração comercial criada ("new-commerce-migration-created")

    Esse evento é gerado quando a nova migração de comércio é criada.

  • Falha na migração de novo comércio ("new-commerce-migration-failed")

    Esse evento é gerado quando a nova migração de comércio é falhada.

  • Falha no novo cronograma de migração do Commerce ("new-commerce-migration-schedule-failed")

    Esse evento é gerado quando o novo cronograma de migração de comércio é falhado.

  • Evento criado por referência ("referral-created")

    Esse evento é gerado quando a referência é criada.

  • Evento de referência atualizada ("referral-updated")

    Este evento é gerado quando a referência é atualizada.

  • Evento criado por referência relacionada ("related-referral-created")

    Esse evento é gerado quando a referência relacionada é criada.

  • Evento de Referência Relacionada Atualizado ("related-referral-updated")

    Este evento é gerado quando a referência relacionada é atualizada.

  • Evento de Subscrição Atualizada ("subscrição atualizada")

    Esse evento é gerado quando a assinatura é alterada. Esses eventos serão gerados quando houver uma alteração interna, além de quando as alterações forem feitas por meio da API do Partner Center.

    Nota

    Há um atraso de até 48 horas entre o momento em que uma assinatura é alterada e quando o evento Assinatura atualizada é acionado.

  • Evento de teste ("test-created")

    Este evento permite-lhe auto-integrar e testar o seu registo, solicitando um evento de teste e, em seguida, acompanhando o seu progresso. Você pode ver as mensagens de falha que estão sendo recebidas da Microsoft ao tentar entregar o evento. Esta restrição aplica-se apenas a eventos "criados por teste". Os dados com mais de sete dias serão limpos.

  • Evento Threshold Exceeded ("usagerecords-thresholdExceeded")

    Esse evento é gerado quando a quantidade de uso do Microsoft Azure para qualquer cliente excede seu orçamento de gastos de uso (seu limite). Para obter mais informações, consulte (Definir um orçamento de gastos do Azure para seus clientes/partner-center/set-an-azure-spending-budget-for-your-customers).

Eventos futuros do Webhook serão adicionados para recursos que mudam no sistema que o parceiro não está no controle, e novas atualizações serão feitas para que esses eventos sejam o mais próximos possível do "tempo real". O feedback dos Parceiros sobre quais eventos agregam valor aos seus negócios será útil para determinar quais novos eventos adicionar.

Para obter uma lista completa dos eventos Webhook suportados pelo Partner Center, consulte Eventos Webhook do Partner Center.

Pré-requisitos

  • Credenciais conforme descrito na autenticação do Partner Center. Este cenário oferece suporte à autenticação com credenciais autônomas de Aplicativo e Aplicativo+Usuário.

Receber eventos do Partner Center

Para receber eventos do Partner Center, você deve expor um ponto de extremidade acessível publicamente. Como esse ponto de extremidade está exposto, você deve validar se a comunicação é do Partner Center. Todos os eventos Webhook que você recebe são assinados digitalmente com um certificado que é encadeado para a raiz da Microsoft. Um link para o certificado usado para assinar o evento também será fornecido. Isso permitirá que o certificado seja renovado sem que você precise reimplantar ou reconfigurar seu serviço. O Partner Center fará 10 tentativas para realizar o evento. Se o evento ainda não for entregue após 10 tentativas, ele será movido para uma fila offline e nenhuma outra tentativa será feita na entrega.

O exemplo a seguir mostra um evento publicado do 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"
}

Nota

O cabeçalho Autorização tem um esquema de "Assinatura". Esta é uma assinatura codificada base64 do conteúdo.

Como autenticar o retorno de chamada

Para autenticar o evento de retorno de chamada recebido do Partner Center, siga estas etapas:

  1. Verifique se os cabeçalhos necessários estão presentes (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Baixe o certificado usado para assinar o conteúdo (x-ms-certificate-url).

  3. Verifique a cadeia de certificados.

  4. Verifique a "Organização" do certificado.

  5. Leia o conteúdo com codificação UTF8 em um buffer.

  6. Crie um provedor de criptografia RSA.

  7. Verifique se os dados correspondem ao que foi assinado com o algoritmo de hash especificado (por exemplo, SHA256).

  8. Se a verificação for bem-sucedida, processe a mensagem.

Nota

Por padrão, o token de assinatura será enviado em um cabeçalho de Autorização. Se você definir SignatureTokenToMsSignatureHeader como true em seu registro, o token de assinatura será enviado no cabeçalho x-ms-signature.

Modelo de evento

A tabela a seguir descreve as propriedades de um evento do Partner Center.

Propriedades

Nome Descrição
Nome do Evento O nome do evento. No formato {resource}-{action}. Por exemplo, "test-created".
ResourceUri O URI do recurso que foi alterado.
ResourceName O nome do recurso que foi alterado.
AuditUrl Opcional. O URI do registro de auditoria.
ResourceChangeUtcDate A data e a hora, no formato UTC, em que ocorreu a alteração do recurso.

Exemplo

O exemplo a seguir mostra a estrutura de um evento do Partner Center.

{
    "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 APIs

Autenticação

Todas as chamadas para as APIs Webhook são autenticadas usando o token Bearer no cabeçalho de autorização. Adquira um token de acesso para acessar https://api.partnercenter.microsoft.como . Esse token é o mesmo usado para acessar o restante das APIs do Partner Center.

Obter uma lista de eventos

Retorna uma lista dos eventos que são atualmente suportados pelas APIs Webhook.

URL do Recurso

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

Exemplo de solicitação

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

Exemplo de resposta

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" ]

Registe-se para receber eventos

Registra um locatário para receber os eventos especificados.

URL do Recurso

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

Exemplo de solicitação

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"]
}

Exemplo de resposta

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" ]
}

Ver um registo

Retorna o registro de evento Webhooks para um locatário.

URL do Recurso

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

Exemplo de solicitação

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

Exemplo de resposta

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"]
}

Atualizar um registro de evento

Atualiza um registro de evento existente.

URL do Recurso

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

Exemplo de solicitação

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"]
}

Exemplo de resposta

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" ]
}

Envie um evento de teste para validar o seu registo

Gera um evento de teste para validar o registro do Webhooks. Este teste destina-se a validar que você pode receber eventos do Partner Center. Os dados desses eventos serão excluídos sete dias após a criação do evento inicial. Você deve estar registrado para o evento "test-created", usando a API de registro, antes de enviar um evento de validação.

Nota

Há um limite de aceleração de 2 solicitações por minuto ao postar um evento de validação.

URL do Recurso

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

Exemplo de solicitação

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:

Exemplo de resposta

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" }

Verificar se o evento foi entregue

Retorna o estado atual do evento de validação. Essa verificação pode ser útil para solucionar problemas de entrega de eventos. A Resposta contém um resultado para cada tentativa feita para entregar o evento.

URL do Recurso

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

Exemplo de solicitação

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

Exemplo de resposta

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"
    }]
}

Exemplo de validação de assinatura

Exemplo de assinatura do controlador de retorno de chamada (ASP.NET)

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

Validação de assinatura

O exemplo a seguir mostra como adicionar um atributo de autorização ao controlador que está recebendo retornos de chamada de eventos Webhook.

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}."));
            }
        }
    }
}