Delen via


Partnercentrum-webhooks

Van toepassing op: Partnercentrum | Partnercentrum beheerd door 21Vianet | Partnercentrum voor Microsoft Cloud voor de Amerikaanse overheid

Juiste rollen: globale beheerder | Facturerings-Beheer | Beheer agent | Verkoopagent | Helpdeskagent

Met de Webhook-API's van Partnercentrum kunnen partners zich registreren voor gebeurtenissen voor resourcewijziging. Deze gebeurtenissen worden geleverd in de vorm van HTTP-POST's naar de geregistreerde URL van de partner. Als u een gebeurtenis van partnercentrum wilt ontvangen, hosten partners een callback waar partnercentrum de gebeurtenis voor het wijzigen van de resource kan posten. De gebeurtenis wordt digitaal ondertekend, zodat de partner kan controleren of deze is verzonden vanuit het Partnercentrum. Webhookmeldingen worden alleen geactiveerd in de omgeving met de nieuwste configuratie voor collectieve verkoop.

Partners kunnen kiezen uit Webhook-gebeurtenissen, zoals de volgende voorbeelden, die worden ondersteund door partnercentrum.

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

    Deze gebeurtenis wordt gegenereerd wanneer er een Azure-fraude-gebeurtenis wordt gedetecteerd.

  • Gedelegeerde Beheer goedgekeurde gebeurtenis voor relatie ('dap-admin-relationship-approved')

    Deze gebeurtenis wordt gegenereerd wanneer de gedelegeerde Beheer-bevoegdheden zijn goedgekeurd door de tenant van de klant.

  • Resellerrelatie geaccepteerd door klantgebeurtenis ('reseller-relationship-accepted-by-customer')

    Deze gebeurtenis wordt gegenereerd wanneer de resellerrelatie wordt goedgekeurd door de tenant van de klant.

  • Gedelegeerde Beheer-relatie beëindigde gebeurtenis ('dap-admin-relationship-terminated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedelegeerde Beheer bevoegdheden zijn beëindigd door de klant.

  • Dap Beheer-relatie beëindigd door Microsoft-gebeurtenis ('dap-admin-relationship-terminated-by-microsoft')

    Deze gebeurtenis wordt gegenereerd wanneer Microsoft DAP beëindigt tussen de partner- en klanttenant wanneer DAP langer dan 90 dagen inactief is.

  • Gedetailleerde Beheer geactiveerde gebeurtenis voor toegangstoewijzing ('granular-admin-access-assignment-activated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer toegangstoewijzing bevoegdheden door de partner wordt geactiveerd zodra de Microsoft Entra-rollen zijn toegewezen aan specifieke beveiligingsgroepen.

  • Gedetailleerde Beheer Gebeurtenis voor het maken van toegangstoewijzing ('granular-admin-access-assignment-created')

    Deze gebeurtenis wordt gegenereerd wanneer de toewijzing van de gedetailleerde gedelegeerde gedelegeerde Beheer bevoegdheden wordt gemaakt door de partner. Partners kunnen door de klant goedgekeurde Microsoft Entra-rollen toewijzen aan specifieke beveiligingsgroepen.

  • Gedetailleerde Beheer Verwijderde gebeurtenis toegangstoewijzing ('granular-admin-access-assignment-deleted')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer toegangstoewijzing bevoegdheden wordt verwijderd door de partner.

  • Gedetailleerde Beheer Bijgewerkte gebeurtenis voor toegangstoewijzing ('granular-admin-access-assignment-updated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer toegangstoewijzing bevoegdheden wordt bijgewerkt door de partner.

  • Gedetailleerde Beheer-relatie geactiveerde gebeurtenis ('granular-admin-relationship-activated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer Bevoegdheden wordt gemaakt en actief is om de klant goed te keuren.

  • Gedetailleerde Beheer goedgekeurde gebeurtenis voor relatie ('granular-admin-relationship-approved')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer bevoegdheden zijn goedgekeurd door de tenant van de klant.

  • Gedetailleerde Beheer-relatie verlopen gebeurtenis ('granular-admin-relationship-expired')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer bevoegdheden verlopen.

  • Gedetailleerde Beheer bijgewerkte gebeurtenis voor relaties ('granular-admin-relationship-updated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer-bevoegdheden worden bijgewerkt door de partner-/klanttenant.

  • Gedetailleerde Beheer automatische uitgebreide relatiegebeurtenis ('granular-admin-relationship-auto-extended')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer Bevoegdheden automatisch wordt uitgebreid door het systeem.

  • Gedetailleerde Beheer beëindigde gebeurtenis van relatie ('granular-admin-relationship-terminated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde Beheer bevoegdheden worden beëindigd door de partner-/klanttenant.

  • Nieuwe commercemigratie voltooid ('new-commerce-migration-completed')

    Deze gebeurtenis wordt gegenereerd wanneer de nieuwe commercemigratie is voltooid.

  • Nieuwe commercemigratie gemaakt ('new-commerce-migration-created')

    Deze gebeurtenis wordt gegenereerd wanneer de nieuwe commercemigratie wordt gemaakt.

  • Migratie van nieuwe commerce is mislukt ('new-commerce-migration-failed')

    Deze gebeurtenis wordt gegenereerd wanneer de nieuwe commercemigratie is mislukt.

  • Migratieschema voor nieuwe commerce is mislukt ('new-commerce-migration-schedule-failed')

    Deze gebeurtenis treedt op wanneer het nieuwe commercemigratieschema is mislukt.

  • Verwijzingsgebeurtenis gemaakt ('verwijzing gemaakt')

    Deze gebeurtenis wordt gegenereerd wanneer de verwijzing wordt gemaakt.

  • Bijgewerkte verwijzingsgebeurtenis ('verwijzing-bijgewerkt')

    Deze gebeurtenis wordt gegenereerd wanneer de verwijzing wordt bijgewerkt.

  • Gerelateerde verwijzingsgebeurtenis gemaakt ('related-referral-created')

    Deze gebeurtenis wordt gegenereerd wanneer de gerelateerde verwijzing wordt gemaakt.

  • Gerelateerde bijgewerkte gebeurtenis voor verwijzing ('related-referral-updated')

    Deze gebeurtenis wordt gegenereerd wanneer de gerelateerde verwijzing wordt bijgewerkt.

  • Gebeurtenis abonnement bijgewerkt ('abonnement bijgewerkt')

    Deze gebeurtenis wordt gegenereerd wanneer het abonnement wordt gewijzigd. Deze gebeurtenissen worden gegenereerd wanneer er een interne wijziging is, naast wanneer er wijzigingen worden aangebracht via de Partnercentrum-API.

    Notitie

    Er is een vertraging van maximaal 48 uur tussen het moment waarop een abonnement wordt gewijzigd en wanneer de gebeurtenis Abonnement bijgewerkt wordt geactiveerd.

  • Testgebeurtenis ('test-created')

    Met deze gebeurtenis kunt u uw registratie zelf onboarden en testen door een testgebeurtenis aan te vragen en vervolgens de voortgang ervan bij te houden. U kunt de foutberichten zien die van Microsoft worden ontvangen tijdens het bezorgen van de gebeurtenis. Deze beperking is alleen van toepassing op gebeurtenissen die zijn gemaakt met een test. Gegevens ouder dan zeven dagen worden verwijderd.

  • Drempelwaarde overschreden gebeurtenis ('usagerecords-thresholdExceeded')

    Deze gebeurtenis wordt gegenereerd wanneer het gebruik van Microsoft Azure voor elke klant het budget voor gebruiksuitgaven overschrijdt (hun drempelwaarde). Zie voor meer informatie (Een Azure-uitgavenbudget instellen voor uw klanten/partnercentrum/set-an-azure-spending-budget-for-your-customers).

Toekomstige Webhook-gebeurtenissen worden toegevoegd voor resources die veranderen in het systeem waarvan de partner geen controle heeft en verdere updates worden uitgevoerd om deze gebeurtenissen zo dicht mogelijk bij 'realtime' te krijgen. Feedback van partners over welke gebeurtenissen waarde toevoegen aan hun bedrijf, is handig bij het bepalen welke nieuwe gebeurtenissen moeten worden toegevoegd.

Zie Partnercentrum-webhook-gebeurtenissen voor een volledige lijst met webhook-gebeurtenissen die worden ondersteund door Partnercentrum.

Vereisten

  • Referenties zoals beschreven in verificatie in partnercentrum. Dit scenario ondersteunt verificatie met zowel zelfstandige app- als app+gebruikersreferenties.

Gebeurtenissen ontvangen van partnercentrum

Als u gebeurtenissen van partnercentrum wilt ontvangen, moet u een openbaar toegankelijk eindpunt beschikbaar maken. Omdat dit eindpunt beschikbaar is, moet u valideren dat de communicatie afkomstig is van partnercentrum. Alle webhook-gebeurtenissen die u ontvangt, worden digitaal ondertekend met een certificaat dat is gekoppeld aan de Microsoft-basis. Er wordt ook een koppeling naar het certificaat opgegeven dat wordt gebruikt om de gebeurtenis te ondertekenen. Hierdoor kan het certificaat worden vernieuwd zonder dat u uw service opnieuw hoeft te implementeren of opnieuw te configureren. In het Partnercentrum worden 10 pogingen ondernomen om de gebeurtenis te leveren. Als de gebeurtenis nog steeds niet wordt geleverd na 10 pogingen, wordt deze verplaatst naar een offlinewachtrij en worden er geen verdere pogingen gedaan bij levering.

In het volgende voorbeeld ziet u een gebeurtenis die is gepost vanuit partnercentrum.

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

Notitie

De autorisatieheader heeft een schema van 'Handtekening'. Dit is een base64-gecodeerde handtekening van de inhoud.

De callback verifiëren

Voer de volgende stappen uit om de callbackgebeurtenis te verifiëren die is ontvangen van partnercentrum:

  1. Controleer of de vereiste headers aanwezig zijn (Autorisatie, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Download het certificaat dat wordt gebruikt om de inhoud te ondertekenen (x-ms-certificate-URL).

  3. Controleer de certificaatketen.

  4. Controleer de organisatie van het certificaat.

  5. Lees de inhoud met UTF8-codering in een buffer.

  6. Maak een RSA Crypto Provider.

  7. Controleer of de gegevens overeenkomen met wat is ondertekend met het opgegeven hash-algoritme (bijvoorbeeld SHA256).

  8. Als de verificatie is geslaagd, verwerkt u het bericht.

Notitie

Standaard wordt het handtekeningtoken verzonden in een autorisatieheader. Als u SignatureTokenToMsSignatureHeader instelt op true in uw registratie, wordt het handtekeningtoken verzonden in de header x-ms-signature.

Gebeurtenismodel

In de volgende tabel worden de eigenschappen van een Partnercentrum-gebeurtenis beschreven.

Eigenschappen

Name Beschrijving
EventName De naam van de gebeurtenis. In het formulier {resource}-{action}. Bijvoorbeeld 'test-created'.
ResourceUri De URI van de resource die is gewijzigd.
Resourcename De naam van de resource die is gewijzigd.
AuditUrl Optioneel. De URI van de auditrecord.
ResourceChangeUtcDate De datum en tijd, in UTC-indeling, wanneer de resourcewijziging heeft plaatsgevonden.

Voorbeeld

In het volgende voorbeeld ziet u de structuur van een partnercentrumgebeurtenis.

{
    "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-API's

Verificatie

Alle aanroepen naar de Webhook-API's worden geverifieerd met behulp van het Bearer-token in de autorisatieheader. Een toegangstoken verkrijgen voor toegang https://api.partnercenter.microsoft.com. Dit token is hetzelfde token dat wordt gebruikt voor toegang tot de rest van de Partner Center-API's.

Een lijst met gebeurtenissen ophalen

Retourneert een lijst met de gebeurtenissen die momenteel worden ondersteund door de Webhook-API's.

Resource URL

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

Aanvraagvoorbeeld

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

Responsvoorbeeld

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

Registreren om gebeurtenissen te ontvangen

Registreert een tenant om de opgegeven gebeurtenissen te ontvangen.

Resource URL

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

Aanvraagvoorbeeld

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

Responsvoorbeeld

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

Een registratie weergeven

Retourneert de webhooks-gebeurtenisregistratie voor een tenant.

Resource URL

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

Aanvraagvoorbeeld

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

Responsvoorbeeld

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

Een gebeurtenisregistratie bijwerken

Hiermee wordt een bestaande gebeurtenisregistratie bijgewerkt.

Resource URL

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

Aanvraagvoorbeeld

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

Responsvoorbeeld

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

Een test gebeurtenis verzenden om uw registratie te valideren

Hiermee wordt een test gebeurtenis gegenereerd om de Webhooks-registratie te valideren. Deze test is bedoeld om te valideren dat u gebeurtenissen van partnercentrum kunt ontvangen. Gegevens voor deze gebeurtenissen worden zeven dagen nadat de eerste gebeurtenis is gemaakt, verwijderd. U moet zijn geregistreerd voor de gebeurtenis 'test-created', met behulp van de registratie-API, voordat u een validatiegebeurtenis verzendt.

Notitie

Er is een beperkingslimiet van 2 aanvragen per minuut bij het plaatsen van een validatie-gebeurtenis.

Resource URL

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

Aanvraagvoorbeeld

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:

Responsvoorbeeld

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

Controleer of de gebeurtenis is bezorgd

Retourneert de huidige status van de validatie-gebeurtenis. Deze verificatie kan nuttig zijn voor het oplossen van problemen met de levering van gebeurtenissen. Het antwoord bevat een resultaat voor elke poging die wordt gedaan om de gebeurtenis te leveren.

Resource URL

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

Aanvraagvoorbeeld

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

Responsvoorbeeld

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

Voorbeeld voor handtekeningvalidatie

Voorbeeld van callbackcontrollerhandtekening (ASP.NET)

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

Handtekeningvalidatie

In het volgende voorbeeld ziet u hoe u een autorisatiekenmerk toevoegt aan de controller die callbacks van Webhook-gebeurtenissen ontvangt.

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