Condividi tramite


Webhook del Centro per i partner

Si applica a: Centro per i partner | Centro per i partner gestito da 21Vianet | Centro per i partner per Microsoft Cloud per il governo degli Stati Uniti

Ruoli appropriati: amministratore globale | Amministratore fatturazione | Agente amministratore | Agente di vendita | Agente helpdesk

Le API webhook del Centro per i partner consentono ai partner di registrarsi per gli eventi di modifica delle risorse. Questi eventi vengono recapitati sotto forma di POST HTTP all'URL registrato del partner. Per ricevere un evento dal Centro per i partner, i partner ospitano un callback in cui il Centro per i partner può PUBBLICARE l'evento di modifica delle risorse. L'evento è firmato digitalmente in modo che il partner possa verificare che sia stato inviato dal Centro per i partner. Le notifiche webhook vengono attivate solo nell'ambiente con la configurazione più recente per la co-selling.

Il Centro per i partner supporta gli eventi Webhook seguenti.

  • Evento di frode di Azure rilevato ("azure-fraud-event-detected")

    Questo evento viene generato quando viene rilevato un evento di frode di Azure.

  • Evento approvato relazione amministratore delegato ("dap-admin-relationship-approved")

    Questo evento viene generato quando i privilegi di amministratore delegato vengono approvati dal tenant del cliente.

  • Relazione rivenditore accettata dall'evento del cliente ("reseller-relationship-accepted-by-customer")

    Questo evento viene generato quando il tenant del cliente approva la relazione reseller.

  • Relazione di rivenditore indiretto accettata dall'evento del cliente ("indirect-reseller-relationship-accepted-by-customer")

    Questo evento viene generato quando il tenant del cliente approva la relazione rivenditore indiretto.

  • Evento di terminazione relazione amministratore delegato ("dap-admin-relationship-terminate")

    Questo evento viene generato quando il cliente termina i privilegi di amministratore delegato.

  • Relazione di amministrazione dap terminata dall'evento Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    Questo evento viene generato quando Microsoft termina DAP tra il tenant partner e il tenant del cliente quando DAP è inattivo per più di 90 giorni.

  • Evento attivato per l'assegnazione granulare dell'accesso amministratore ("granular-admin-access-assignment-activated")

    Questo evento viene generato quando il partner attiva l'assegnazione di accesso Granular Delegated Admin Privileges dopo che i ruoli di Microsoft Entra vengono assegnati a gruppi di sicurezza specifici.

  • Evento creato per l'assegnazione granulare dell'accesso amministratore ("granular-admin-access-assignment-created")

    Questo evento viene generato quando il partner crea l'assegnazione di accesso granulare ai privilegi di amministratore delegato. I partner possono assegnare ruoli Microsoft Entra approvati dai clienti a specifici gruppi di sicurezza.

  • Granular Admin Access Assignment Deleted Event ("granular-admin-access-assignment-deleted")

    Questo evento viene generato quando il partner elimina l'assegnazione di accesso granulare ai privilegi di amministratore delegato.

  • Evento aggiornato dell'assegnazione granulare dell'accesso amministratore ("granular-admin-access-assignment-updated")

    Questo evento viene generato quando il partner aggiorna l'assegnazione di accesso granulare ai privilegi di amministratore delegato.

  • Evento attivato per la relazione di amministrazione granulare ("granular-admin-relationship-activated")

    Questo evento viene generato quando vengono creati i privilegi di amministratore delegato granulari e attivi per consentire al cliente di approvare.

  • Evento approvato per la relazione di amministrazione granulare ("granular-admin-relationship-approved")

    Questo evento viene generato quando il tenant del cliente approva i privilegi di amministratore delegato granulari.

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

    Questo evento viene generato quando i privilegi di amministratore delegato granulari sono scaduti.

  • Evento di creazione della relazione di amministrazione granulare ("granular-admin-relationship-created")

    Questo evento viene generato quando vengono creati i privilegi di amministratore delegato granulari.

  • Evento aggiornato della relazione di amministrazione granulare ("granular-admin-relationship-updated")

    Questo evento viene generato quando il cliente o il partner aggiorna i privilegi di amministratore delegato granulari.

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

    Questo evento viene generato quando il sistema estende automaticamente i privilegi di amministratore delegato granulari.

  • Evento granulare di terminazione della relazione di amministrazione ("granular-admin-relationship-terminate")

    Questo evento viene generato quando il partner o il tenant del cliente termina i privilegi di amministratore delegato granulari.

  • Evento pronto per la fattura ("pronto per la fattura")

    Questo evento viene generato quando la nuova fattura è pronta.

  • Nuova migrazione commerciale completata ("new-commerce-migration-completed")

    Questo evento viene generato al termine della nuova migrazione commerciale.

  • Nuova migrazione commerciale creata ("new-commerce-migration-created")

    Questo evento viene generato quando viene creata la nuova migrazione commerciale.

  • Nuova migrazione commerciale non riuscita ("new-commerce-migration-failed")

    Questo evento viene generato quando la nuova migrazione commerciale non è riuscita.

  • Crea trasferimento ("create-transfer")

    Questo evento viene generato quando viene creato il trasferimento.

  • Trasferimento aggiornamenti ("aggiornamento-trasferimento")

    Questo evento viene generato quando il trasferimento viene aggiornato.

  • Trasferimento completo ("trasferimento completo")

    Questo evento viene generato al termine del trasferimento.

  • Fail Transfer ("fail-transfer")

    Questo evento viene generato quando il trasferimento non riesce.

  • Nuova pianificazione della migrazione commerciale non riuscita ("new-commerce-migration-schedule-failed")

    Questo evento viene generato quando la nuova pianificazione della migrazione commerciale non è riuscita.

  • Referral Created Event ("referral-created")

    Questo evento viene generato quando viene creata la segnalazione.

  • Evento aggiornato delle segnalazioni ("aggiornamento delle segnalazioni")

    Questo evento viene generato quando viene aggiornata la segnalazione.

  • Evento di segnalazione correlato creato ("related-referral-created")

    Questo evento viene generato quando viene creata la segnalazione correlata.

  • Evento aggiornato delle segnalazioni correlate ("related-referral-updated")

    Questo evento viene generato quando viene aggiornata la segnalazione correlata.

  • Evento attivo della sottoscrizione ("subscription-active")

    Questo evento viene generato quando viene attivata la sottoscrizione.

    Nota

    Il webhook Attivo sottoscrizione e l'evento del log attività corrispondente sono disponibili solo per i tenant sandbox in questo momento.

  • Evento in sospeso della sottoscrizione ("sottoscrizione in sospeso")

    Questo evento viene generato quando l'ordine corrispondente è stato ricevuto correttamente e la creazione della sottoscrizione è in sospeso.

    Nota

    Il webhook sottoscrizione in sospeso e l'evento del log attività corrispondente sono attualmente disponibili solo per i tenant sandbox.

  • Evento rinnovato della sottoscrizione ("rinnovo della sottoscrizione")

    Questo evento viene generato quando la sottoscrizione completa il rinnovo.

    Nota

    Il webhook rinnovato della sottoscrizione e l'evento del log attività corrispondente sono attualmente disponibili solo per i tenant sandbox.

  • Evento aggiornato della sottoscrizione ("subscription-updated")

    Questo evento viene generato quando cambia la sottoscrizione. Questi eventi vengono generati quando si verifica una modifica interna oltre a quando vengono apportate modifiche tramite l'API del Centro per i partner.

    Nota

    Si verifica un ritardo di fino a 48 ore tra il momento in cui viene modificata una sottoscrizione e quando viene attivato l'evento Subscription Updated.

  • Evento test ("test-created")

    Questo evento consente di eseguire l'onboarding automatico e testare la registrazione richiedendo un evento di test e quindi monitorandone lo stato. È possibile visualizzare i messaggi di errore ricevuti da Microsoft durante il tentativo di recapitare l'evento. Questa restrizione si applica solo agli eventi "creati dal test". I dati precedenti a sette giorni vengono eliminati.

  • Evento soglia superata ("usagerecords-thresholdExceeded")

    Questo evento viene generato quando la quantità di utilizzo di Microsoft Azure per qualsiasi cliente supera il budget di spesa di utilizzo (soglia). Per altre informazioni, vedere (Impostare un budget di spesa di Azure per i clienti/partner-center/set-an-azure-spending-budget-for-your-customers).

Verranno aggiunti eventi webhook futuri per le risorse che cambiano nel sistema di cui il partner non è in controllo e verranno apportati ulteriori aggiornamenti per ottenere tali eventi il più vicino possibile al "tempo reale". Il feedback dei partner su cui gli eventi aggiungono valore alla propria azienda è utile per determinare quali nuovi eventi aggiungere.

Per un elenco completo degli eventi webhook supportati dal Centro per i partner, vedere Eventi webhook del Centro per i partner.

Prerequisiti

Ricezione di eventi dal Centro per i partner

Per ricevere eventi dal Centro per i partner, è necessario esporre un endpoint accessibile pubblicamente. Poiché questo endpoint è esposto, è necessario verificare che la comunicazione provena dal Centro per i partner. Tutti gli eventi webhook ricevuti vengono firmati digitalmente con un certificato concatenato alla radice Microsoft. Viene fornito anche un collegamento al certificato usato per firmare l'evento. In questo modo il certificato può essere rinnovato senza dover ridistribuire o riconfigurare il servizio. Il Centro per i partner effettua 10 tentativi di consegnare l'evento. Se l'evento non viene ancora recapitato dopo 10 tentativi, viene spostato in una coda offline e non vengono eseguiti ulteriori tentativi al recapito.

L'esempio seguente mostra un evento pubblicato dal Centro per i partner.

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

L'intestazione Authorization ha uno schema "Signature". Si tratta di una firma con codifica Base64 del contenuto.

Come autenticare il callback

Per autenticare l'evento di callback ricevuto dal Centro per i partner, seguire questa procedura:

  1. Verificare che le intestazioni necessarie siano presenti (Autorizzazione, x-ms-certificate-url, x-ms-signature-algorithm).
  2. Scarica il certificato usato per firmare il contenuto (x-ms-certificate-url).
  3. Verificare la catena di certificati.
  4. Verificare l'"organizzazione" del certificato.
  5. Leggere il contenuto con codifica UTF8 in un buffer.
  6. Crea un provider di crittografia RSA.
  7. Verificare che i dati corrispondano a quanto firmato con l'algoritmo hash specificato ,ad esempio SHA256.
  8. Se la verifica ha esito positivo, elaborare il messaggio.

Nota

Per impostazione predefinita, il token di firma viene inviato in un'intestazione di autorizzazione. Se si imposta SignatureTokenToMsSignatureHeader su true nella registrazione, il token di firma viene invece inviato nell'intestazione x-ms-signature.

Modello di evento

Nella tabella seguente vengono descritte le proprietà di un evento del Centro per i partner.

Proprietà

Nome Descrizione
EventName Nome dell'evento. Nel formato {resource}-{action}. Ad esempio, "test-created".
ResourceUri URI della risorsa modificata.
ResourceName Nome della risorsa modificata.
AuditUrl Facoltativo. URI del record Audit.
ResourceChangeUtcDate Data e ora, in formato UTC, quando si è verificata la modifica della risorsa.

Esempio

L'esempio seguente illustra la struttura di un evento del Centro per i partner.

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

Autenticazione

Tutte le chiamate alle API webhook vengono autenticate usando il token bearer nell'intestazione dell'autorizzazione. Acquisire un token di accesso per accedere https://api.partnercenter.microsoft.coma . Questo token è lo stesso token usato per accedere al resto delle API del Centro per i partner.

Ottenere un elenco di eventi

Restituisce un elenco degli eventi attualmente supportati dalle API webhook.

URL risorsa

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

Esempio di richiesta

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

Risposta di esempio

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

Registrarsi per ricevere gli eventi

Registra un tenant per ricevere gli eventi specificati.

URL risorsa

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

Esempio di richiesta

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

Risposta di esempio

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

Visualizzare una registrazione

Restituisce la registrazione dell'evento Webhooks per un tenant.

URL risorsa

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

Esempio di richiesta

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

Risposta di esempio

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

Aggiornare una registrazione di eventi

Aggiorna una registrazione eventi esistente.

URL risorsa

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

Esempio di richiesta

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

Risposta di esempio

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

Inviare un evento di test per convalidare la registrazione

Genera un evento di test per convalidare la registrazione dei webhook. Questo test è progettato per verificare che sia possibile ricevere eventi dal Centro per i partner. I dati per questi eventi vengono eliminati sette giorni dopo la creazione dell'evento iniziale. È necessario essere registrati per l'evento "test-created", usando l'API di registrazione, prima di inviare un evento di convalida.

Nota

Quando si pubblica un evento di convalida, è previsto un limite di 2 richieste al minuto.

URL risorsa

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

Esempio di richiesta

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:

Risposta di esempio

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

Verificare che l'evento sia stato recapitato

Restituisce lo stato corrente dell'evento di convalida. Questa verifica può essere utile per la risoluzione dei problemi di recapito degli eventi. La risposta contiene un risultato per ogni tentativo effettuato per recapitare l'evento.

URL risorsa

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

Esempio di richiesta

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

Risposta di esempio

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

Esempio di convalida della firma

Firma del controller di callback di esempio (ASP.NET)

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

Convalida della firma

Nell'esempio seguente viene illustrato come aggiungere un attributo di autorizzazione al controller che riceve i callback dagli eventi 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}."));
            }
        }
    }
}