Webhooks del Centro de Socios

Aplica a: Centro de socios | Centro de socios operado por 21Vianet | Centro de socios para Microsoft Cloud para el Gobierno de EE. UU.

Roles apropiados: Administrador de facturación | Agente de administración | Agente de ventas | Agente de soporte técnico

En lugar de comprobar constantemente las API del Centro de partners para obtener actualizaciones, los partners pueden configurar webhooks para obtener notificaciones instantáneas cuando cambia algo, como la suscripción de un cliente o una oferta de Marketplace. Siempre que se produzca un evento específico, el sistema envía un HTTP POST a la dirección URL registrada del asociado, para que puedan responder inmediatamente. Por ejemplo, los asociados pueden controlar rápidamente los cambios de suscripción o realizar un seguimiento de las migraciones a medida que se producen.

Ventajas clave:

  • Flujos de trabajo automatizados: Los webhooks se encargan de las comprobaciones rutinarias, por lo que los asociados no tienen que buscar actualizaciones manualmente. Esto simplifica los procesos empresariales y ahorra tiempo.
  • Alertas inmediatas: Los partners reciben una notificación tan pronto como se produzcan eventos importantes, como la suscripción de un cliente que se va a cambiar, suspender o cancelar. Esto significa que pueden actuar más rápido y proporcionar un mejor servicio.
  • Administración de ofertas de SaaS: Los publicadores pueden usar webhooks para mantener el estado de las suscripciones de SaaS sincronizadas con el Centro de partners, lo que garantiza que ambos lados de la transacción permanezcan actualizados.
  • Seguimiento de la migración: Los webhooks ayudan a los asociados a automatizar y supervisar las migraciones de suscripciones de clientes a la nueva plataforma comercial. Mayor eficiencia: al utilizar notificaciones de eventos en lugar de buscar actualizaciones mediante sondeo, los asociados reducen la carga de trabajo en sus sistemas y en las API del Centro de Partners.

Cómo funcionan los webhooks del Centro de socios

Las APIs de webhook del Centro de Partners permiten a los socios 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 configuran un callback para que el Centro de Partners pueda publicar el evento de cambio de recursos. El evento está firmado digitalmente para que el socio pueda verificar que se envió desde el Centro de Socios. Las notificaciones de webhook solo se desencadenan en el entorno que tiene la configuración más reciente para Co-sell.

El Centro de asociados admite los siguientes eventos de Webhook.

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

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

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

    Este evento se genera cuando el inquilino del cliente aprueba los privilegios de administrador delegado.

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

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

  • Evento de aceptación de relación de revendedor indirecto por el cliente ("indirect-reseller-relationship-accepted-by-customer")

    Este evento se genera cuando la entidad del cliente aprueba la relación con el revendedor indirecto.

  • Evento de relación terminada de administrador delegado ("dap-admin-relationship-terminated")

    Este evento se genera cuando el cliente finaliza los privilegios de administrador delegado.

  • Evento de Terminación de Relación de Administrador Dap por Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    Este evento se genera cuando Microsoft finaliza la DAP entre la entidad del socio y la entidad del cliente cuando la DAP ha estado inactiva durante más de 90 días.

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

    Este evento se genera cuando el socio activa la asignación de acceso de Privilegios de Administrador Delegado Granular una vez que se asignan los roles de Microsoft Entra a grupos de seguridad específicos.

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

    Este evento se genera cuando el socio crea la asignación de acceso a privilegios granulares de administrador delegado. Los asociados pueden asignar roles de Microsoft Entra aprobados por el cliente a grupos de seguridad específicos.

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

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

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

    Este evento se genera cuando el socio actualiza la asignación de acceso a privilegios de administrador delegados granulares.

  • Evento de activación de relación de administrador granular ("granular-admin-relationship-activated")

    Este evento se genera cuando se crean los privilegios de administrador delegado pormenorizados y se activan para que el cliente apruebe.

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

    Este evento se genera cuando el cliente aprueba los Privilegios de Administración Delegada Granular.

  • Evento de expiración de relación de administrador granular ("granular-admin-relationship-expired")

    Este evento se emite cuando expiran los privilegios de administrador delegado granulares.

  • Evento de creación de relación de administrador granular ("granular-admin-relationship-created")

    Este evento se genera cuando se crea el Granular Delegated Admin Privileges.

  • Evento de actualización de relación de administrador granular ("granular-admin-relationship-updated")

    Este evento se genera cuando el cliente o el asociado actualizan los privilegios de administrador 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 extiende automáticamente los privilegios de administrador delegado pormenorizados.

  • Evento terminado de relación de administrador granular ("granular-admin-relationship-terminated")

    Este evento se genera cuando el socio o el tenant del cliente finaliza los privilegios granulares de administrador delegado.

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

    Este evento se activa cuando se completa la migración al nuevo modelo comercial.

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

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

  • Error en la migración a Nuevo Comercio ("new-commerce-migration-failed")

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

  • Crear transferencia ("create-transfer")

    Este evento se genera cuando se crea la transferencia.

  • Update Transfer ("update-transfer")

    Este evento se genera cuando se actualiza la transferencia.

  • Transferencia completa ("transferencia completa")

    Este evento se genera cuando se completa la transferencia.

  • Transferencia de expiración ("transferencia de expiración")

    Este evento se genera cuando la transferencia ha expirado.

  • Transferencia fallida ("transferencia fallida")

    Este evento se genera cuando se produce un error en la transferencia.

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

    Este evento se genera cuando falla el nuevo cronograma de migración comercial.

  • Evento de creación de referencia ("creación de referencia")

    Este evento se genera cuando se crea la referencia.

  • Evento de actualización de referencia ("referencia actualizada")

    Este evento se genera cuando se actualiza la referencia.

  • Evento de Creación de Referencia Relacionada ("related-referral-created")

    Este evento se genera cuando se crea la remisión asociada.

  • Evento de actualización de referencia relacionada ("related-referral-updated")

    Este evento se genera cuando se actualiza la referencia relacionada.

  • Evento activo de suscripción ("subscription-active")

    Este evento se genera cuando se activa la suscripción.

    Nota:

    El Subscription Active webhook y el evento de Registro de Actividad correspondiente solo están disponibles para los sandbox tenants en este momento.

  • Evento de suscripción pendiente ("subscription-pending")

    Este evento se genera cuando el pedido correspondiente se ha recibido correctamente y la creación de la suscripción está pendiente.

    Nota:

    El webhook de suscripción pendiente y el evento de registro de actividad correspondiente solo están disponibles para los tenants de Sandbox por el momento.

  • Evento de renovación de suscripción ("suscripción renovada")

    Este evento se genera cuando se completa la renovación de la suscripción.

    Nota:

    El webhook de renovación de suscripción y el evento del registro de actividades correspondiente solo están disponibles para los entornos de prueba en este momento.

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

    Este evento se genera cuando cambia la suscripción. Estos eventos se generan cuando se produce un cambio interno además de cuando se realizan 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 purgan.

  • Evento umbral superado ("usagerecords-thresholdExceededed")

    Este evento se genera cuando la cantidad de uso de Microsoft Azure para cualquier cliente excede su presupuesto de uso (el umbral establecido). Para obtener más información, consulte (Establecer 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 resulta útil para determinar qué nuevos eventos agregar.

  • Evento creado de solicitud de descarga ("download-request-created")

Este evento se genera cuando algunos crean una descarga mediante AI Assist.

  • Evento de solicitud de descarga completada ("download-request-completed")

Este evento se genera cuando se ha completado una solicitud de descarga y está disponible para descargar desde la página de descarga del área de trabajo del cliente.

Requisitos previos

  • Credenciales según se describen en Partner Center authentication. Este escenario admite la autenticación con credenciales de aplicación independiente y app+usuario.

Recepción de eventos del Centro de socios

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 proviene del Centro de Partners. Todos los eventos de Webhook que recibe están firmados digitalmente con un certificado conectado al certificado raíz de Microsoft. También se proporciona un vínculo al certificado que se usa para firmar el evento. Esto permite renovar el certificado sin tener que volver a implementar o volver a configurar el servicio. El Centro de asociados realiza 10 intentos para gestionar el evento. Si el evento todavía no se entrega después de 10 intentos, se mueve a una cola offline y no se realizan más intentos de 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.

Cómo autenticar 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 UTF-8 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 envía en un encabezado Authorization. Si establece SignatureTokenToMsSignatureHeader en true en el registro, el token de firma se envía en su lugar en el encabezado x-ms-signature.

Modelo de eventos

En la tabla siguiente se describen las propiedades de un evento de Partner Center.

Propiedades

Name Descripción
EventName El nombre del evento. En el formato {resource}-{action}. Por ejemplo, "creado por prueba".
ResourceUri Identificador URI del recurso que cambió.
ResourceName Nombre del recurso que cambió.
AuditUrl Opcional. 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 socios.

{
    "EventName": "test-created",
    "ResourceUri": "http://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/aaaabbbb-0000-cccc-1111-dddd2222eeee",
    "ResourceName": "test",
    "AuditUri": null,
    "ResourceChangeUtcDate": "2017-11-16T16:19:06.3520276+00:00"
}

APIs 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 a https://api.partnercenter.microsoft.com. 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: aaaa0000-bb11-2222-33cc-444444dddddd
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: bbbb1111-cc22-3333-44dd-555555eeeeee
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 cliente.

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: cccc2222-dd33-4444-55ee-666666ffffff
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

Actualiza 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: bbbb1111-cc22-3333-44dd-555555eeeeee
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á diseñada para validar que usted puede recibir eventos del Centro de Partners. Los datos de estos eventos se eliminan 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: dddd3333-ee44-5555-66ff-777777aaaaaa
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: eeee4444-ff55-6666-77aa-888888bbbbbb
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

{ "correlationId": "eeee4444-ff55-6666-77aa-888888bbbbbb" }

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/eeee4444-ff55-6666-77aa-888888bbbbbb
MS-CorrelationId: dddd3333-ee44-5555-66ff-777777aaaaaa
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: ffff5555-aa66-7777-88bb-999999cccccc
MS-RequestId: 0843bdb2-113a-4926-a51c-284aa01d722e
X-Locale: en-US

{
    "correlationId": "eeee4444-ff55-6666-77aa-888888bbbbbb",
    "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

Definición de ejemplo de un controlador de devolución de llamada (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 StreamReaders;
            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}."));
            }
        }
    }
}