Delen via


Partnercentrum-webhooks

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

Juiste rollen: globale beheerder | Factureringsbeheerder | Beheerderagent | 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.

Partnercentrum ondersteunt de volgende Webhook-gebeurtenissen.

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

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

  • Goedgekeurde gebeurtenis voor gedelegeerde beheerdersrelatie ('dap-admin-relationship-approved')

    Deze gebeurtenis wordt gegenereerd wanneer de gedelegeerde beheerdersbevoegdheden worden goedgekeurd door de tenant van de klant.

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

    Deze gebeurtenis wordt gegenereerd wanneer de tenant van de klant de resellerrelatie goedkeurt.

  • Indirecte resellerrelatie geaccepteerd door klantgebeurtenis ('indirect-reseller-relationship-accepted-by-customer')

    Deze gebeurtenis wordt gegenereerd wanneer de tenant van de klant de indirecte resellerrelatie goedkeurt.

  • De gedelegeerde beheerrelatie beëindigde gebeurtenis ('dap-admin-relationship-terminated')

    Deze gebeurtenis wordt gegenereerd wanneer de klant de gedelegeerde beheerdersbevoegdheden beëindigt.

  • Dap-beheerrelatie 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 geactiveerde gebeurtenis voor beheerderstoegang ('granular-admin-access-assignment-activated')

    Deze gebeurtenis wordt gegenereerd wanneer de partner de toewijzing gedetailleerde gedelegeerde beheerdersbevoegdheden activeert zodra de Microsoft Entra-rollen zijn toegewezen aan specifieke beveiligingsgroepen.

  • Gedetailleerde gebeurtenis voor beheerderstoegang gemaakt ('granular-admin-access-assignment-created')

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

  • Gedetailleerde toewijzing van beheerderstoegang verwijderd ('granular-admin-access-assignment-deleted')

    Deze gebeurtenis wordt gegenereerd wanneer de partner de toewijzing gedetailleerde gedelegeerde beheerdersbevoegdheden verwijdert.

  • Gedetailleerde gebeurtenis voor beheerderstoegang bijgewerkt ('granular-admin-access-assignment-updated')

    Deze gebeurtenis wordt gegenereerd wanneer de partner de toewijzing van gedetailleerde gedelegeerde beheerdersbevoegdheden bijwerkt.

  • Gedetailleerde gebeurtenis voor geactiveerde beheerrelatie ('granular-admin-relationship-activated')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde beheerdersbevoegdheden worden gemaakt en actief zijn om de klant goed te keuren.

  • Gedetailleerde door de beheerder goedgekeurde gebeurtenis ('granular-admin-relationship-approved')

    Deze gebeurtenis wordt gegenereerd wanneer de tenant van de klant de gedetailleerde gedelegeerde beheerdersbevoegdheden goedkeurt.

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

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde beheerdersbevoegdheden zijn verlopen.

  • Gedetailleerde door de beheerder gemaakte gebeurtenis ('granular-admin-relationship-created')

    Deze gebeurtenis wordt gegenereerd wanneer de gedetailleerde gedelegeerde beheerdersbevoegdheden worden gemaakt.

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

    Deze gebeurtenis wordt gegenereerd wanneer de klant of partner de gedetailleerde gedelegeerde beheerdersbevoegdheden bijwerken.

  • Gedetailleerde beheerrelatie automatische uitgebreide gebeurtenis ('granular-admin-relationship-auto-extended')

    Deze gebeurtenis wordt gegenereerd wanneer het systeem automatisch de gedetailleerde gedelegeerde beheerdersbevoegdheden uitbreidt.

  • Gedetailleerde beheerrelatie beëindigde gebeurtenis ('granular-admin-relationship-terminated')

    Deze gebeurtenis wordt gegenereerd wanneer de partner- of klanttenant de gedetailleerde gedelegeerde beheerdersbevoegdheden beëindigt.

  • Gebeurtenis gereed voor factuur ('factur-ready')

    Deze gebeurtenis wordt gegenereerd wanneer de nieuwe factuur gereed is.

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

  • Overdracht maken ('create-transfer')

    Deze gebeurtenis wordt gegenereerd wanneer de overdracht wordt gemaakt.

  • Updateoverdracht ('update-transfer')

    Deze gebeurtenis wordt gegenereerd wanneer de overdracht wordt bijgewerkt.

  • Volledige overdracht ('complete-transfer')

    Deze gebeurtenis wordt gegenereerd wanneer de overdracht is voltooid.

  • Mislukte overdracht ('fail-transfer')

    Deze gebeurtenis wordt gegenereerd wanneer de overdracht 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.

  • Actieve abonnementsgebeurtenis ('abonnement-actief')

    Deze gebeurtenis wordt gegenereerd wanneer het abonnement wordt geactiveerd.

    Notitie

    De actieve webhook van het abonnement en de bijbehorende gebeurtenis van het activiteitenlogboek zijn momenteel alleen beschikbaar voor sandbox-tenants.

  • Gebeurtenis abonnement in behandeling (abonnement in behandeling)

    Deze gebeurtenis wordt gegenereerd wanneer de bijbehorende bestelling is ontvangen en het maken van een abonnement in behandeling is.

    Notitie

    De webhook Abonnement in behandeling en de bijbehorende gebeurtenis van het activiteitenlogboek zijn momenteel alleen beschikbaar voor Sandbox-tenants.

  • Abonnement verlengde gebeurtenis ('abonnement verlengd')

    Deze gebeurtenis wordt gegenereerd wanneer het abonnement de verlenging voltooit.

    Notitie

    De abonnementsvernieuwde webhook en de bijbehorende gebeurtenis van het activiteitenlogboek zijn op dit moment alleen beschikbaar voor sandbox-tenants.

  • 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 die ouder zijn dan zeven dagen, worden opgeschoond.

  • 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. Partnercentrum doet 10 pogingen 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 in plaats daarvan 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: aaaa0000-bb11-2222-33cc-444444dddddd
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: 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" ]
}

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

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 na het maken van de eerste gebeurtenis 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: dddd3333-ee44-5555-66ff-777777aaaaaa
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: eeee4444-ff55-6666-77aa-888888bbbbbb
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

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

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

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