Partilhar via


Webhooks do Centro de Parceiros

Aplica-se a: Partner Center | Partner Center operado pela 21Vianet | Partner Center para a Nuvem Microsoft para o Governo dos EUA

Funções apropriadas: Administrador de cobrança | 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 configuram um callback onde o Partner Center pode enviar o evento de alteração de recurso. O evento é 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.

O Partner Center suporta os seguintes eventos Webhook.

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

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

  • Evento de Aprovação de Relacionamento de Administrador Delegado ("dap-admin-relationship-approved")

    Este evento é gerado quando os Privilégios de Administrador Delegado são aprovados pelo cliente.

  • Evento de Aceitação de Relacionamento de Revendedor pelo Cliente ("relacionamento-de-revendedor-aceito-pelo-cliente")

    Esse evento é gerado quando o locatário do cliente aprova o Relacionamento de Revendedor.

  • Evento de Aceitação da Relação de Revendedor Indireto pelo Cliente ("indirect-reseller-relationship-accepted-by-customer")

    Esse evento é gerado quando o locatário do cliente aprova o Relacionamento de Revendedor Indireto.

  • Evento de Rescisão de Relação de Administrador Delegado ("dap-admin-relationship-terminated")

    Esse evento é gerado quando o cliente encerra os Privilégios de Administrador Delegado.

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

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

  • Evento de Ativação de Atribuição de Acesso Administrativo Granular ("granular-admin-access-assignment-activated")

    Esse evento é acionado quando o parceiro ativa a atribuição de acesso dos Privilégios Granulares de Administrador Delegado, depois que as funções do Microsoft Entra são atribuídas a grupos de segurança específicos.

  • Evento de Atribuição de Acesso de Administrador Granular Criado ("granular-admin-access-assignment-created")

    Esse evento é gerado quando o parceiro cria a atribuição de acesso Granular Delegated Admin Privileges. Os parceiros podem atribuir funções do Microsoft Entra aprovadas pelo cliente a grupos de segurança específicos.

  • Evento de Eliminação de Atribuição de Acesso Administrativo Granular ("granular-admin-access-assignment-deleted")

    Este evento é gerado quando o parceiro elimina a atribuição de acesso dos Privilégios de Administrador Delegado Granular.

  • Evento de Atualização de Atribuição de Acesso do Administrador Granular ("granular-admin-access-assignment-updated")

    Este evento é ativado quando o parceiro atualiza a atribuição de acesso dos privilégios granulares de administração delegada.

  • Evento de Ativação da Relação de Administração Granular ("granular-admin-relationship-activated")

    Este evento é gerado quando os Privilégios Granulares de Administrador Delegado são criados e estão ativos para aprovação do cliente.

  • Evento de Aprovação de Relação de Administração Granular ("granular-admin-relationship-approved")

    Este evento é gerado quando o cliente aprova os Privilégios de Administração Delegada Granular.

  • Evento Relação Administrativa Granular Expirada ("granular-admin-relationship-expired")

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

  • Evento de criação de relação de administrador granular ("granular-admin-relationship-created")

    Esse evento é gerado quando os Privilégios de Administrador Delegado Granular são criados.

  • Evento de Atualização da Relação Granular de Administração ("granular-admin-relationship-updated")

    Esse evento é gerado quando o cliente ou parceiro atualiza os Privilégios de Administrador Delegado Granular.

  • Evento de Expansão Automática da Relação com o Administrador Granular ("granular-admin-relationship-auto-extended")

    Esse evento é gerado quando o sistema estende automaticamente os privilégios de administrador delegado granular.

  • Evento de Terminação de Relacionamento do Administrador Granular ("granular-admin-relationship-terminated")

    Esse evento é gerado quando o parceiro ou locatário do cliente encerra os Privilégios de Administrador Delegado Granular.

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

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

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

    Este evento é acionado quando é criada uma nova migração de comércio.

  • Migração de novo comércio falhou ("new-commerce-migration-failed")

    Este evento ocorre quando a nova migração de comércio falha.

  • Criar transferência ("create-transfer")

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

  • Transferência de atualização ("update-transfer")

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

  • Transferência Completa ("transferência completa")

    Este evento é acionado quando a transferência é concluída.

  • Transferência Expirada ("expire-transfer")

    Esse evento é gerado quando a transferência expira.

  • Falha na transferência ("fail-transfer")

    Esse evento é gerado quando a transferência falha.

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

    Este evento é gerado quando o novo cronograma de migração do comércio falha.

  • Evento de Referência Criada ("referral-created")

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

  • Evento de atualização de referência ("referral-updated")

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

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

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

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

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

  • Evento Ativo de Subscrição ("subscrição-ativa")

    Esse evento é gerado quando a assinatura é ativada.

    Nota

    O webhook Subscription Ative e o Evento de Registro de Atividades correspondente só estão disponíveis para locatários da Sandbox no momento.

  • Evento de Subscrição Pendente ("subscrição pendente")

    Esse evento é gerado quando o pedido correspondente foi recebido com sucesso e a criação da assinatura está pendente.

    Nota

    O webhook Subscrição Pendente e o Evento de Registo de Atividades correspondente estão apenas disponíveis para clientes da Sandbox atualmente.

  • Evento de Renovação de Subscrição ("subscrição renovada")

    Este evento é gerado quando a assinatura conclui a renovação.

    Nota

    O webhook Subscrição Renovada e o Evento de Registo de Atividades correspondente só estão disponíveis para utilizadores do sandbox neste momento.

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

    Esse evento é gerado quando a assinatura é alterada. Esses eventos são gerados quando há uma alteração interna, além de quando as alterações são 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 são limpos.

  • Evento Limite Excedido ("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 controla, e atualizações adicionais serão realizadas para aproximar esses eventos o máximo possível do "tempo real". O feedback dos Parceiros sobre quais eventos agregam valor aos seus negócios é ú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 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, deve expor um endpoint acessível publicamente. Como este endpoint está exposto, deve validar se a comunicação é proveniente 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 é fornecido. Isso permite que o certificado seja renovado sem que você precise reimplantar ou reconfigurar seu serviço. O Partner Center faz 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 é 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 {recurso}-{ação}. 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 aceder https://api.partnercenter.microsoft.com. Esse token é o mesmo usado para acessar o restante das APIs do Partner Center.

Obter uma lista de eventos

Retorna uma lista dos eventos 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: aaaa0000-bb11-2222-33cc-444444dddddd
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: 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" ]
}

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

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 sã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

Existe uma limitação de 2 solicitações por minuto ao enviar 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: dddd3333-ee44-5555-66ff-777777aaaaaa
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: eeee4444-ff55-6666-77aa-888888bbbbbb
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

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

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/eeee4444-ff55-6666-77aa-888888bbbbbb
MS-CorrelationId: dddd3333-ee44-5555-66ff-777777aaaaaa
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: 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"
    }]
}

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