Webhooks del Centro de partners

Se aplica a: Centro de partners | Centro de partners operado por 21Vianet | Centro de partners para Microsoft Cloud for US Government

Roles adecuados: Administrador global | Administración de facturación | agente de Administración | Agente de ventas | Agente del departamento de soporte técnico

Las API de webhook del Centro de partners permiten a los asociados registrarse para eventos de cambio de recursos. Estos eventos se entregan en forma de HTTP POST a la dirección URL registrada del partner. Para recibir un evento del Centro de partners, los partners hospedarán una devolución de llamada en la que el Centro de partners puede usar POST para el evento de cambio de recursos. El evento se firmará digitalmente para que el partner pueda verificar que se ha enviado desde el Centro de partners. Las notificaciones de webhook solo se desencadenan en el entorno que tiene la configuración más reciente para la venta conjunta.

Los partners pueden seleccionar los eventos de webhook, como los ejemplos siguientes, que son compatibles con el Centro de partners.

  • Evento de fraude de Azure detectado ("azure-fraud-event-detected")

    Este evento se genera cuando se detecta un evento de fraude de Azure.

  • Evento Administración relación aprobado delegado ("dap-admin-relationship-approved")

    Este evento se genera cuando el inquilino del cliente aprobó los privilegios Administración delegados.

  • Relación de revendedor aceptada por evento de cliente ("reseller-relationship-accepted-by-customer")

    Este evento se genera cuando el inquilino del cliente aprueba la relación de revendedor.

  • Evento Administración relación terminated ("dap-admin-relationship-terminated")

    Este evento se genera cuando el cliente terminó el Administración privilegios delegados.

  • Dap Administración relación terminated by Microsoft Event ("dap-admin-relationship-terminated-by-microsoft")

    Este evento se genera cuando Microsoft finaliza DAP entre el inquilino del partner y el cliente cuando DAP está inactivo durante más de 90 días.

  • Evento activado de asignación de acceso granular Administración ("granular-admin-access-assignment-activated")

    Este evento se genera cuando el asociado activa la asignación de acceso a privilegios delegados pormenorizado Administración una vez que los roles de Microsoft Entra se asignan a grupos de seguridad específicos.

  • Evento granular Administración asignación de acceso creado ("granular-admin-access-assignment-created")

    Este evento se genera cuando el asociado crea la asignación de acceso a privilegios delegados pormenorizados Administración. Los partners pueden asignar roles de Microsoft Entra aprobados por el cliente a grupos de seguridad específicos.

  • Evento eliminado de asignación de acceso granular Administración ("granular-admin-access-assignment-deleted")

    Este evento se genera cuando el asociado elimina la asignación de acceso a privilegios delegados pormenorizados Administración.

  • Evento actualizado de asignación de acceso granular Administración ("granular-admin-access-assignment-updated")

    Este evento se genera cuando el asociado actualiza la asignación de acceso a privilegios delegados pormenorizados Administración privilegios.

  • Evento activado por Administración relación granular ("granular-admin-relationship-activated")

    Este evento se genera cuando se crea el Administración privilegios delegados pormenorizados y se activa para que el cliente apruebe.

  • Evento de relación aprobado por Administración granular ("granular-admin-relationship-approved")

    Este evento se genera cuando el inquilino del cliente aprobó los privilegios delegados pormenorizados Administración.

  • Evento expirado de relación Administración granular ("granular-admin-relationship-expired")

    Este evento se genera cuando expira el Administración privilegios delegados pormenorizados.

  • Evento actualizado de relación Administración granular ("granular-admin-relationship-updated")

    Este evento se genera cuando el inquilino del partner o cliente actualiza el Administración privilegios delegados pormenorizados.

  • Evento extendido automático de relación de Administración granular ("granular-admin-relationship-auto-extended")

    Este evento se genera cuando el sistema amplía automáticamente el Administración privilegios delegados pormenorizados.

  • Evento granular Administración relación terminated ("granular-admin-relationship-terminated")

    Este evento se genera cuando el inquilino del asociado o cliente termina el Administración Privilegios delegados pormenorizados.

  • Nueva migración comercial completada ("new-commerce-migration-completed")

    Este evento se genera cuando se completa la nueva migración comercial.

  • Nueva migración comercial creada ("new-commerce-migration-created")

    Este evento se genera cuando se crea la nueva migración comercial.

  • Error de nueva migración de comercio ("new-commerce-migration-failed")

    Este evento se genera cuando se produce un error en la nueva migración comercial.

  • Error de nueva programación de migración de comercio ("new-commerce-migration-schedule-failed")

    Este evento se genera cuando se produce un error en la nueva programación de migración de comercio.

  • Evento creado por referencias ("creado por referencias")

    Este evento se genera cuando se crea la referencia.

  • Evento actualizado de referencia ("referencia actualizada")

    Este evento se genera cuando se actualiza la referencia.

  • Evento creado por referencias relacionadas ("related-referral-created")

    Este evento se genera cuando se crea la referencia relacionada.

  • Evento actualizado de referencia relacionada ("related-referral-updated")

    Este evento se genera cuando se actualiza la referencia relacionada.

  • Evento actualizado de suscripción ("suscripción actualizada")

    Este evento se genera cuando cambia la suscripción. Estos eventos se generarán cuando haya un cambio interno además de cuando se realicen cambios a través de la API del Centro de partners.

    Nota:

    Hay un retraso de hasta 48 horas entre el momento en que cambia una suscripción y cuando se desencadena el evento Subscription Updated.

  • Evento de prueba ("test-created")

    Este evento le permite incorporarse automáticamente y probar el registro solicitando un evento de prueba y, a continuación, realizando un seguimiento de su progreso. Puede ver los mensajes de error que se reciben de Microsoft al intentar entregar el evento. Esta restricción solo se aplica a eventos "creados por pruebas". Los datos anteriores a siete días se purgarán.

  • Evento umbral superado ("usagerecords-thresholdExceededed")

    Este evento se genera cuando la cantidad de uso de Microsoft Azure para cualquier cliente supera su presupuesto de gasto de uso (su umbral). Para más información, consulte (Establecimiento de un presupuesto de gasto de Azure para los clientes/partner-center/set-an-azure-spending-budget-for-your-customers).

Se agregarán eventos de webhook futuros para los recursos que cambian en el sistema de los que el asociado no está en control y se realizarán más actualizaciones para obtener esos eventos lo más cerca posible de "tiempo real". Los comentarios de los partners sobre qué eventos agregan valor a su negocio serán útiles para determinar qué nuevos eventos agregar.

Para obtener una lista completa de eventos de webhook admitidos por el Centro de partners, consulte Eventos de webhook del Centro de partners.

Requisitos previos

  • Credenciales tal como se describen en el artículo Autenticación del Centro de partners. Este escenario admite la autenticación con credenciales de aplicación independiente y app+usuario.

Recepción de eventos del Centro de partners

Para recibir eventos del Centro de partners, debe exponer un punto de conexión accesible públicamente. Dado que este punto de conexión está expuesto, debe validar que la comunicación es del Centro de partners. Todos los eventos de webhook que reciba se firman digitalmente con un certificado que se encadena a la raíz de Microsoft. También se proporcionará un vínculo al certificado usado para firmar el evento. Esto permitirá que el certificado se renueve sin tener que volver a implementar o volver a configurar el servicio. El Centro de partners realizará 10 intentos para entregar el evento. Si el evento todavía no se entrega después de 10 intentos, se moverá a una cola sin conexión y no se realizarán más intentos en la entrega.

En el ejemplo siguiente se muestra un evento publicado desde el Centro de partners.

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:

El encabezado Authorization tiene un esquema de "Firma". Se trata de una firma codificada en base64 del contenido.

Autenticación de la devolución de llamada

Para autenticar el evento de devolución de llamada recibido del Centro de partners, siga estos pasos:

  1. Compruebe que los encabezados necesarios están presentes (Autorización, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Descargue el certificado usado para firmar el contenido (x-ms-certificate-url).

  3. Compruebe la cadena de certificados.

  4. Compruebe la "Organización" del certificado.

  5. Lea el contenido con codificación UTF8 en un búfer.

  6. Cree un proveedor de cifrado RSA.

  7. Compruebe que los datos coinciden con lo que se firmó con el algoritmo hash especificado (por ejemplo, SHA256).

  8. Si la comprobación se realiza correctamente, procese el mensaje.

Nota:

De forma predeterminada, el token de firma se enviará en un encabezado authorization. Si establece SignatureTokenToMsSignatureHeader en true en el registro, el token de firma se enviará en su lugar en el encabezado x-ms-signature.

Modelo de eventos

En la tabla siguiente se describen las propiedades de un evento del Centro de partners.

Propiedades

Name Descripción
EventName El nombre del evento. En el formulario {resource}-{action}. Por ejemplo, "test-created".
ResourceUri Identificador URI del recurso que cambió.
ResourceName Nombre del recurso que cambió.
AuditUrl Opcional. Identificador URI del registro de auditoría.
ResourceChangeUtcDate Fecha y hora, en formato UTC, cuando se produjo el cambio del recurso.

Ejemplo

En el ejemplo siguiente se muestra la estructura de un evento del Centro de partners.

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

API de webhook

Autenticación

Todas las llamadas a las API de Webhook se autentican mediante el token de portador en el encabezado de autorización. Adquiera un token de acceso para acceder https://api.partnercenter.microsoft.coma . Este token es el mismo token que se usa para acceder al resto de las API del Centro de partners.

Obtener una lista de eventos

Devuelve una lista de los eventos admitidos actualmente por las API de webhook.

Dirección URL del recurso

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

Ejemplo de solicitud

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

Ejemplo de respuesta

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

Registro para recibir eventos

Registra un inquilino para recibir los eventos especificados.

Dirección URL del recurso

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

Ejemplo de solicitud

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

Ejemplo de respuesta

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

Visualización de un registro

Devuelve el registro de eventos de Webhooks para un inquilino.

Dirección URL del recurso

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

Ejemplo de solicitud

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

Ejemplo de respuesta

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

Actualización de un registro de eventos

Novedades un registro de eventos existente.

Dirección URL del recurso

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

Ejemplo de solicitud

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

Ejemplo de respuesta

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

Envío de un evento de prueba para validar el registro

Genera un evento de prueba para validar el registro de webhooks. Esta prueba está pensada para validar que puede recibir eventos del Centro de partners. Los datos de estos eventos se eliminarán siete días después de crear el evento inicial. Debe registrarse para el evento "creado por pruebas", mediante la API de registro, antes de enviar un evento de validación.

Nota:

Hay un límite de 2 solicitudes por minuto al publicar un evento de validación.

Dirección URL del recurso

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

Ejemplo de solicitud

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:

Ejemplo de respuesta

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

Comprobación de que el evento se entregó

Devuelve el estado actual del evento de validación. Esta comprobación puede ser útil para solucionar problemas de entrega de eventos. La respuesta contiene un resultado para cada intento que se realiza para entregar el evento.

Dirección URL del recurso

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

Ejemplo de solicitud

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

Ejemplo de respuesta

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

Ejemplo de validación de firmas

Firma de controlador de devolución de llamada de ejemplo (ASP.NET)

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

Validación de firmas

En el ejemplo siguiente se muestra cómo agregar un atributo de autorización al controlador que recibe devoluciones de llamada de eventos de 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}."));
            }
        }
    }
}