Partner Center-Webhooks

Gilt für: Partner Center | Partner Center-Betrieb durch 21Vianet | Partner Center für Microsoft Cloud for US Government

Geeignete Rollen: Globaler Administrator | Abrechnungsadministrator | Administrator-Agent | Vertriebsmitarbeiter | Helpdesk-Agent

Mit den Partner Center-Webhook-APIs können Partner sich für Ressourcenänderungsereignisse registrieren. Diese Ereignisse werden in Form von HTTP-POSTs an die registrierte URL des Partners übermittelt. Um ein Ereignis von Partner Center zu empfangen, hosten Partner einen Rückruf, der angibt, wo Partner Center das Ressourcenänderungsereignis per POST veröffentlichen kann. Das Ereignis wird digital signiert, damit sich der Partner vergewissern kann, dass es von Partner Center gesendet wurde. Webhook-Benachrichtigungen werden nur für die Umgebung ausgelöst, die über die neueste Konfiguration für co-sell verfügt.

Partner können aus Webhook-Ereignissen wie den folgenden Beispielen auswählen, die vom Partner Center unterstützt werden.

  • Azure Fraud Event Detected ("azure-fraud-event-detected")

    Dieses Ereignis wird ausgelöst, wenn das Azure-Betrugsereignis erkannt wird.

  • Delegierte Administratorbeziehung genehmigtes Ereignis ("dap-admin-relationship-approved")

    Dieses Ereignis wird ausgelöst, wenn die delegierten Administratorrechte vom Kundenmandanten genehmigt wurden.

  • Reseller Relationship Accepted by Customer Event ("Reseller-relationship-accepted-by-customer")

    Dieses Ereignis wird ausgelöst, wenn die Reseller-Beziehung vom Kundenmandanten genehmigt wird.

  • Ereignis "Delegierte Administratorbeziehung beendet" ("dap-admin-relationship-terminated")

    Dieses Ereignis wird ausgelöst, wenn die delegierten Administratorrechte vom Kunden beendet wurden.

  • Dap Admin Relationship Terminated By Microsoft Event ("dap-admin-relationship-terminated-by-microsoft")

    Dieses Ereignis wird ausgelöst, wenn Microsoft DAP zwischen Partner und Kundenmandanten beendet, wenn DAP länger als 90 Tage inaktiv ist.

  • Granular Admin Access Assignment Activated Event ("granular-admin-access-assignment-activated")

    Dieses Ereignis wird ausgelöst, wenn die Zugriffszuweisung für differenzierte delegierte Administratorrechte vom Partner aktiviert wird, sobald die Microsoft Entra-Rollen bestimmten Sicherheitsgruppen zugewiesen wurden.

  • Granular Admin Access Assignment Created Event ("granular-admin-access-assignment-created")

    Dieses Ereignis wird ausgelöst, wenn die Zugriffszuweisung für differenzierte delegierte Administratoren vom Partner erstellt wird. Partner können Kunden genehmigte Microsoft Entra-Rollen bestimmten Sicherheitsgruppen zuweisen.

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

    Dieses Ereignis wird ausgelöst, wenn die Zugriffszuweisung für differenzierte delegierte Administratoren vom Partner gelöscht wird.

  • Granular Admin Access Assignment Updated Event ("granular-admin-access-assignment-updated")

    Dieses Ereignis wird ausgelöst, wenn die Zugriffszuweisung für differenzierte delegierte Administratoren vom Partner aktualisiert wird.

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

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte erstellt und aktiv sind, damit der Kunde dies genehmigen kann.

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

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte vom Kundenmandanten genehmigt wurden.

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

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte abgelaufen sind.

  • Granular Admin Relationship Updated Event ("granular-admin-relationship-updated")

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte vom Partner-/Kundenmandanten aktualisiert werden.

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

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte automatisch vom System erweitert werden.

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

    Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte entweder vom Partner-/Kundenmandanten beendet werden.

  • Neue Commerce-Migration abgeschlossen ("new-commerce-migration-completed")

    Dieses Ereignis wird ausgelöst, wenn die neue E-Commerce-Migration abgeschlossen ist.

  • Neue Commerce-Migration erstellt ("new-commerce-migration-created")

    Dieses Ereignis wird ausgelöst, wenn die neue Commerce-Migration erstellt wird.

  • New Commerce Migration Failed ("new-commerce-migration-failed")

    Dieses Ereignis wird ausgelöst, wenn die neue E-Commerce-Migration fehlgeschlagen ist.

  • Neuer Handelsmigrationszeitplan fehlgeschlagen ("New-commerce-migration-schedule-failed")

    Dieses Ereignis wird ausgelöst, wenn der neue Zeitplan für die E-Commerce-Migration fehlgeschlagen ist.

  • Empfehlung erstellt Ereignis ("Empfehlung erstellt")

    Dieses Ereignis wird ausgelöst, wenn die Empfehlung erstellt wird.

  • Empfehlungs-Aktualisiertes Ereignis ("Empfehlung aktualisiert")

    Dieses Ereignis wird ausgelöst, wenn die Empfehlung aktualisiert wird.

  • Related Referral Created Event ("related-referral-created")

    Dieses Ereignis wird ausgelöst, wenn die zugehörige Empfehlung erstellt wird.

  • Related Referral Updated Event ("related-referral-updated")

    Dieses Ereignis wird ausgelöst, wenn die zugehörige Empfehlung aktualisiert wird.

  • Subscription Updated Event ("subscription-updated")

    Dieses Ereignis wird ausgelöst, wenn sich das Abonnement ändert. Diese Ereignisse werden generiert, wenn zusätzlich zu änderungen über die Partner Center-API eine interne Änderung vorhanden ist.

    Hinweis

    Es gibt eine Verzögerung von bis zu 48 Stunden zwischen dem Zeitpunkt, zu dem sich ein Abonnement ändert und das Ereignis "Abonnement aktualisiert" ausgelöst wird.

  • Testereignis ("test-created")

    Mit diesem Ereignis können Sie Ihre Registrierung selbst onboarden und testen, indem Sie ein Testereignis anfordern und dann den Fortschritt nachverfolgen. Sie können die Fehlermeldungen sehen, die von Microsoft empfangen werden, während Sie versuchen, das Ereignis zu übermitteln. Diese Einschränkung gilt nur für "test-created"-Ereignisse. Daten, die älter als sieben Tage sind, werden gelöscht.

  • Threshold Exceeded-Ereignis ("usagerecords-thresholdExceeded")

    Dieses Ereignis wird ausgelöst, wenn der Betrag der Microsoft Azure-Nutzung für jeden Kunden das Budget für Nutzungsausgaben (deren Schwellenwert) überschreitet. Weitere Informationen finden Sie unter (Festlegen eines Azure-Ausgabenbudgets für Ihre Kunden/partner-center/set-an-azure-spending-budget-for-your-customers).

Zukünftige Webhook-Ereignisse werden für Ressourcen hinzugefügt, die sich im System ändern, auf das der Partner nicht kontrolliert wird, und weitere Updates werden vorgenommen, um diese Ereignisse so nah wie möglich in Echtzeit zu erhalten. Feedback von Partnern, welche Ereignisse ihrem Unternehmen Einen Mehrwert verleihen, ist hilfreich, um zu bestimmen, welche neuen Ereignisse hinzugefügt werden sollen.

Eine vollständige Liste der webhook-Ereignisse, die vom Partner Center unterstützt werden, finden Sie unter Partner Center-Webhook-Ereignisse.

Voraussetzungen

  • Anmeldeinformationen, wie unter Partner Center-Authentifizierung beschrieben. Dieses Szenario unterstützt die Authentifizierung mit eigenständigen App- und App+Benutzeranmeldeinformationen.

Empfangen von Ereignissen vom Partner Center

Um Ereignisse vom Partner Center zu empfangen, müssen Sie einen öffentlich zugänglichen Endpunkt verfügbar machen. Da dieser Endpunkt verfügbar gemacht wird, müssen Sie überprüfen, ob die Kommunikation vom Partner Center stammt. Alle webhook-Ereignisse, die Sie empfangen, werden digital mit einem Zertifikat signiert, das mit dem Microsoft-Stamm verkettet ist. Ein Link zum Zertifikat, das zum Signieren des Ereignisses verwendet wird, wird ebenfalls bereitgestellt. Dadurch kann das Zertifikat erneuert werden, ohne dass Sie Ihren Dienst erneut bereitstellen oder neu konfigurieren müssen. Partner Center führt 10 Versuche zum Übermitteln des Ereignisses durch. Wenn das Ereignis nach 10 Versuchen immer noch nicht übermittelt wird, wird es in eine Offlinewarteschlange verschoben, und es werden keine weiteren Versuche zur Zustellung durchgeführt.

Das folgende Beispiel zeigt ein ereignis, das aus Partner Center veröffentlicht wurde.

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

Hinweis

Der Autorisierungsheader weist ein Schema von "Signatur" auf. Dies ist eine base64-codierte Signatur des Inhalts.

So authentifizieren Sie den Rückruf

Führen Sie die folgenden Schritte aus, um das vom Partner Center empfangene Rückrufereignis zu authentifizieren:

  1. Überprüfen Sie, ob die erforderlichen Header vorhanden sind (Autorisierung, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Lade das Zertifikat herunter, das zum Signieren des Inhalts verwendet wurde (x-ms-certificate-url).

  3. Überprüfen Sie die Zertifikatkette.

  4. Überprüfen Sie die "Organisation" des Zertifikats.

  5. Lesen Sie den Inhalt mit UTF8-Codierung in einen Puffer.

  6. Erstelle einen RSA-Kryptografieanbieter.

  7. Überprüfen Sie, welche Daten mit dem angegebenen Hashalgorithmus signiert wurden (z. B. SHA256).

  8. Wenn die Überprüfung erfolgreich ist, verarbeiten Sie die Nachricht.

Hinweis

Standardmäßig wird das Signaturtoken in einem Autorisierungsheader gesendet. Wenn Sie signatureTokenToMsSignatureHeader in Ihrer Registrierung auf "true" festlegen, wird das Signaturtoken stattdessen im x-ms-signature-Header gesendet.

Ereignismodell

In der folgenden Tabelle werden die Eigenschaften eines Partner Center-Ereignisses beschrieben.

Eigenschaften

Name des Dataflows Beschreibung
EventName Der Name des Ereignisses. Im Formular {resource}-{action}. Beispiel: "test-created".
ResourceUri Der URI der Ressource, die geändert wurde.
ResourceName Der Name der Ressource, die geändert wurde.
AuditUrl Optional. Der URI des Überwachungsdatensatzes.
ResourceChangeUtcDate Datum und Uhrzeit im UTC-Format, wenn die Ressourcenänderung aufgetreten ist.

Beispiel

Das folgende Beispiel zeigt die Struktur eines Partner Center-Ereignisses.

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

Authentifizierung

Alle Aufrufe der Webhook-APIs werden mithilfe des Bearer-Tokens im Autorisierungsheader authentifiziert. Abrufen eines Zugriffstokens für den Zugriff https://api.partnercenter.microsoft.com. Dieses Token ist dasselbe Token, das für den Zugriff auf die restlichen Partner Center-APIs verwendet wird.

Abrufen einer Liste von Ereignissen

Gibt eine Liste der Ereignisse zurück, die derzeit von den Webhook-APIs unterstützt werden.

Ressourcen-URL

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

Anforderungsbeispiel

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

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 183
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: c0bcf3a3-46e9-48fd-8e05-f674b8fd5d66
MS-RequestId: 79419bbb-06ee-48da-8221-e09480537dfc
X-Locale: en-US

[ "subscription-updated", "test-created", "usagerecords-thresholdExceeded" ]

Registrieren für den Empfang von Ereignissen

Registriert einen Mandanten, um die angegebenen Ereignisse zu empfangen.

Ressourcen-URL

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

Anforderungsbeispiel

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

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 718f2336-8b56-4f42-93ac-54896047c59a
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2

{
    "SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": [ "subscription-updated", "test-created" ]
}

Anzeigen einer Registrierung

Gibt die Webhooks-Ereignisregistrierung für einen Mandanten zurück.

Ressourcen-URL

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

Anforderungsbeispiel

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

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 341
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: c3b88ab0-b7bc-48d6-8c55-4ae6200f490a
MS-RequestId: ca30367d-4b24-4516-af08-74bba6dc6657
X-Locale: en-US

{
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": ["subscription-updated", "test-created"]
}

Aktualisieren einer Ereignisregistrierung

Aktualisiert eine vorhandene Ereignisregistrierung.

Ressourcen-URL

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

Anforderungsbeispiel

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

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 718f2336-8b56-4f42-93ac-54896047c59a
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2

{
    "SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
    "WebhookUrl": "{{YourCallbackUrl}}",
    "WebhookEvents": [ "subscription-updated", "test-created" ]
}

Senden eines Testereignisses zum Überprüfen Ihrer Registrierung

Generiert ein Testereignis, um die Webhooks-Registrierung zu überprüfen. Dieser Test soll überprüfen, ob Sie Ereignisse vom Partner Center empfangen können. Daten für diese Ereignisse werden sieben Tage nach der Erstellung des ersten Ereignisses gelöscht. Sie müssen für das Ereignis "test-created" mit der Registrierungs-API registriert sein, bevor Sie ein Überprüfungsereignis senden.

Hinweis

Beim Bereitstellen eines Überprüfungsereignisses gibt es eine Drosselungsgrenze von 2 Anforderungen pro Minute.

Ressourcen-URL

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

Anforderungsbeispiel

POST /webhooks/v1/registration/validationEvents
MS-CorrelationId: 3ef0202b-9d00-4f75-9cff-15420f7612b3
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length:

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 181
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 04af2aea-d413-42db-824e-f328001484d1
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

{ "correlationId": "04af2aea-d413-42db-824e-f328001484d1" }

Überprüfen, ob das Ereignis übermittelt wurde

Gibt den aktuellen Status des Überprüfungsereignisses zurück. Diese Überprüfung kann hilfreich sein, um Probleme bei der Ereignisübermittlung zu beheben. Die Antwort enthält ein Ergebnis für jeden Versuch, das Ereignis zu übermitteln.

Ressourcen-URL

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

Anforderungsbeispiel

GET /webhooks/v1/registration/validationEvents/04af2aea-d413-42db-824e-f328001484d1
MS-CorrelationId: 3ef0202b-9d00-4f75-9cff-15420f7612b3
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate

Beispielantwort

HTTP/1.1 200
Status: 200
Content-Length: 469
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: 497e0a23-9498-4d6c-bd6a-bc4d6d0054e7
MS-RequestId: 0843bdb2-113a-4926-a51c-284aa01d722e
X-Locale: en-US

{
    "correlationId": "04af2aea-d413-42db-824e-f328001484d1",
    "partnerId": "00234d9d-8c2d-4ff5-8c18-39f8afc6f7f3",
    "status": "completed",
    "callbackUrl": "{{YourCallbackUrl}}",
    "results": [{
        "responseCode": "OK",
        "responseMessage": "",
        "systemError": false,
        "dateTimeUtc": "2017-12-08T21:39:48.2386997"
    }]
}

Beispiel für signaturüberprüfung

Beispielsignatur des Rückrufcontrollers (ASP.NET)

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

Signaturüberprüfung

Das folgende Beispiel zeigt, wie Sie dem Controller, der Rückrufe von Webhook-Ereignissen empfängt, ein Autorisierungsattribut hinzufügen.

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