Compartilhar via


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 de administração | Agente de vendas | Agente de helpdesk

As APIs de 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 hospedam um retorno de chamada em que o Partner Center pode POSTAR o evento de alteração de recurso. O evento é 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 venda conjunta.

O Partner Center dá suporte aos seguintes eventos de Webhook.

  • 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 são aprovados pelo locatário do cliente.

  • Relação de revendedor aceita pelo evento do cliente ("relação de revendedor aceito pelo cliente")

    Esse evento é gerado quando o locatário do cliente aprova a Relação de Revendedor.

  • Relação de revendedor indireto aceita pelo evento do cliente ("relação indireta de revendedor aceito pelo cliente")

    Esse evento é gerado quando o locatário do cliente aprova a Relação de Revendedor Indireto.

  • Evento encerrado de relacionamento de administrador delegado ("dap-admin-relationship-terminated")

    Esse evento é gerado quando o cliente encerra os privilégios de administrador delegado.

  • Relacionamento de administrador do Dap encerrado pelo evento da Microsoft ("dap-admin-relationship-terminated-by-microsoft")

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

  • Evento de atribuição de acesso de administrador granular ativado ("granular-admin-access-assignment-activated")

    Esse evento é gerado quando o parceiro ativa a atribuição de acesso Granular Delegated Admin Privileges 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 atribuição de acesso de administrador granular excluído ("granular-admin-access-assignment-deleted")

    Esse evento é gerado quando o parceiro exclui a atribuição de acesso Granular Delegated Admin Privileges.

  • Evento de atualização de atribuição de acesso de administrador granular ("granular-admin-access-assignment-updated")

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

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

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

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

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

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

    Esse evento é gerado quando os privilégios de administrador delegado granular expiram.

  • Granular Admin Relationship Created Event ("granular-admin-relationship-created")

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

  • Evento de atualização do relacionamento de administrador granular ("granular-admin-relationship-updated")

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

  • Granular Admin Relationship Auto Extended Event ("granular-admin-relationship-auto-extended")

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

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

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

  • Evento de fatura pronta ("fatura pronta")

    Esse evento é gerado quando a nova fatura está pronta.

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

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

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

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

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

  • Criar transferência ("criar-transferir")

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

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

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

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

    Esse evento é gerado quando a transferência é concluída.

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

    Esse evento é gerado quando a transferência falha.

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

    Esse evento é gerado quando o novo agendamento de migração de comércio falha.

  • Evento de Indicação Criada ("criação por indicação")

    Esse evento é gerado quando a indicação é criada.

  • Evento de Indicação Atualizada ("referência atualizada")

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

  • Evento de Criação de Referência Relacionada ("related-referral-created")

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

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

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

  • Evento ativo de assinatura ("assinatura ativa")

    Esse evento é gerado quando a assinatura é ativada.

  • Evento pendente de assinatura ("assinatura pendente")

    Esse evento é gerado quando a assinatura está pendente.

  • Evento de Renovação de Assinatura ("assinatura renovada")

    Esse evento é gerado quando a assinatura é renovada.

  • Evento de atualização de assinatura ("atualização de assinatura")

    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.

    Observação

    Há um atraso de até 48 horas entre o momento em que uma assinatura é alterada e quando o evento Subscription Updated é disparado.

  • Evento de teste ("criado por teste")

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

  • Evento de 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 de webhook serão adicionados para recursos que mudam no sistema que o parceiro não está no controle, e outras atualizações serão feitas para obter esses eventos o mais próximo possível do "tempo real". Os comentários dos parceiros sobre quais eventos agregam valor aos negócios são úteis para determinar quais novos eventos adicionar.

Para obter uma lista completa de eventos de webhook compatíveis com o Partner Center, consulte Eventos de webhook do Partner Center.

Pré-requisitos

  • Credenciais, conforme descrito em Autenticação do Partner Center. Esse cenário dá 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 de Webhook que você recebe são assinados digitalmente com um certificado que se encadeia à 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 entregar 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 "Signature". 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 (Autorização, 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 é 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. Na forma {resource}-{action}. Por exemplo, "criado por teste".
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.

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 de portador no cabeçalho de autorização. Adquira um token de acesso para acessar https://api.partnercenter.microsoft.como . Esse token é o mesmo token usado para acessar o restante das APIs do Partner Center.

Obtenha uma lista de eventos

Retorna uma lista dos eventos atualmente suportados pelas APIs do 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" ]

Inscreva-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 seu registro

Gera um evento de teste para validar o registro de Webhooks. Esse teste destina-se a validar se 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 "criado por teste", usando a API de registro, antes de enviar um evento de validação.

Observação

Há um limite de limitaçã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: 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" }

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