Webhooks do Partner Center

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

Funções apropriadas: Administrador global | Administrador de faturamento | Agente administrativo | Agente de vendas | Agente de helpdesk

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 POSTs HTTP para a URL registrada do parceiro. Para receber um evento do Partner Center, os parceiros hospedarão um retorno de chamada em que o Partner Center pode POSTAR o evento de alteração de recurso. O evento será assinado digitalmente para que o parceiro possa verificar se ele foi enviado do Partner Center. As notificações de Webhook são acionadas apenas para o ambiente que tem a configuração mais recente para Co-sell.

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

  • Evento de Fraude do Azure Detectado ("azure-fraud-event-detected")

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

  • 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 com Revendedor Aceito pelo Cliente ("Relacionamento com Revendedor Aceito pelo Cliente")

    Esse evento é gerado quando o Relacionamento de 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 Parceiro e 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 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 Granular Admin Access Assignment Created ("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 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 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 Granular Admin Relationship Auto Extended ("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.

  • Migração de Novo Comércio Concluída ("new-commerce-migration-completed")

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

  • Nova migração de comércio criada ("new-commerce-migration-created")

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

  • Falha na migração do New Commerce ("new-commerce-migration-failed")

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

  • Falha na agenda de migração do New Commerce ("new-commerce-migration-schedule-failed")

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

  • Evento criado por referência ("criado por referência")

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

  • Evento atualizado de referência ("atualizado por referência")

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

  • Evento criado por referência relacionada ("relacionado criado por referência")

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

  • Evento atualizado de referência relacionado ("relacionado à referência atualizado")

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

  • Evento Atualizado da Assinatura ("Assinatura 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.

    Observação

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

    Esse evento permite que você se auto-integre e teste seu registro solicitando um evento de teste e, em seguida, acompanhe seu progresso. Você pode ver as mensagens de falha que estão sendo recebidas da Microsoft ao tentar entregar o evento. Essa restrição só se aplica a eventos "criados por teste". Os dados com mais de sete dias serão eliminados.

  • 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).

Futuros eventos Webhook serão adicionados para recursos que mudam no sistema que o parceiro não controla, e mais atualizações serão feitas para obter esses eventos o mais próximo 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 devem ser adicionados.

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

Pré-requisitos

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

Recebendo 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 o Microsoft Root. 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 postado 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"
}

Observação

O cabeçalho Authorization tem um esquema de "Assinatura". Esta é uma assinatura codificada em 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 hash especificado (por exemplo, SHA256).

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

Observação

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
EventName 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, quando ocorreu a alteração do recurso.

Amostra

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

Cadastre-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 sua inscrição

Gera um evento de teste para validar o registro de 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.

Observação

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

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