Dela via


Partnercenter – webhooks

Gäller för: Partnercenter | Partnercenter som drivs av 21Vianet | Partnercenter för Microsoft Cloud for US Government

Lämpliga roller: Global administratör | Faktureringsadministratör | Administratörsagent | Försäljningsagent | Helpdesk-agent

Med Api:erna för Partnercenter Webhook kan partner registrera sig för resursändringshändelser. Dessa händelser levereras i form av HTTP-POST:er till partnerns registrerade URL. För att ta emot en händelse från Partnercenter är partnern värd för ett återanrop där Partnercenter kan PUBLICERA resursändringshändelsen. Händelsen är digitalt signerad så att partnern kan verifiera att den har skickats från Partnercenter. Webhook-meddelanden utlöses endast till den miljö som har den senaste konfigurationen för säljsamarbete.

Partnercenter stöder följande Webhook-händelser.

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

    Den här händelsen utlöses när Azure-bedrägerihändelsen identifieras.

  • Godkänd händelse för delegerad administratörsrelation ("dap-admin-relationship-approved")

    Den här händelsen utlöses när delegerade administratörsbehörigheter godkänns av kundklientorganisationen.

  • Reseller-relation accepterad av kundhändelse ("reseller-relationship-accepted-by-customer")

    Den här händelsen utlöses när kundklientorganisationen godkänner återförsäljarrelationen.

  • Indirekt resellerrelation accepterad av kundhändelse ("indirect-reseller-relationship-accepted-by-customer")

    Den här händelsen utlöses när kundklientorganisationen godkänner den indirekta återförsäljarrelationen.

  • Avslutad händelse för delegerad administratörsrelation ("dap-admin-relationship-terminated")

    Den här händelsen utlöses när kunden avslutar delegerade administratörsprivilegier.

  • Dap Admin-relation avslutad av Microsoft-händelse ("dap-admin-relationship-terminated-by-microsoft")

    Den här händelsen utlöses när Microsoft avslutar DAP mellan partner- och kundklientorganisationen när DAP är inaktivt i mer än 90 dagar.

  • Detaljerad aktiverad händelse för administratörsåtkomsttilldelning ("granular-admin-access-assignment-activated")

    Den här händelsen utlöses när partnern aktiverar granular Delegated Admin Privileges-åtkomsttilldelningen när Microsoft Entra-rollerna har tilldelats till specifika säkerhetsgrupper.

  • Detaljerad administratörsåtkomsttilldelning skapad händelse ("granular-admin-access-assignment-created")

    Den här händelsen utlöses när partnern skapar åtkomsttilldelningen Detaljerade delegerade administratörsprivilegier. Partner kan tilldela kundgodkända Microsoft Entra-roller till specifika säkerhetsgrupper.

  • Detaljerad administratörsåtkomsttilldelning Borttagen händelse ("granular-admin-access-assignment-deleted")

    Den här händelsen utlöses när partnern tar bort åtkomsttilldelningen Detaljerade delegerade administratörsprivilegier.

  • Detaljerad administratörsåtkomsttilldelning Uppdaterad händelse ("granular-admin-access-assignment-updated")

    Den här händelsen utlöses när partnern uppdaterar åtkomsttilldelningen Granular Delegated Admin Privileges.

  • Detaljerad aktiverad händelse för administratörsrelation ("granular-admin-relationship-activated")

    Den här händelsen utlöses när de detaljerade delegerade administratörsbehörigheterna skapas och är aktiva för kunden att godkänna.

  • Detaljerad administratörsrelation godkänd händelse ("granular-admin-relationship-approved")

    Den här händelsen utlöses när kundklientorganisationen godkänner de detaljerade delegerade administratörsbehörigheterna.

  • Detaljerad händelse för administratörsrelationen har upphört att gälla ("granular-admin-relationship-expired")

    Den här händelsen utlöses när de detaljerade delegerade administratörsbehörigheterna har upphört att gälla.

  • Detaljerad administratörsrelation skapad händelse ("granular-admin-relationship-created")

    Den här händelsen utlöses när de detaljerade delegerade administratörsbehörigheterna skapas.

  • Detaljerad administratörsrelation uppdaterad händelse ("granular-admin-relationship-updated")

    Den här händelsen utlöses när antingen kunden eller partnern uppdaterar de detaljerade delegerade administratörsbehörigheterna.

  • Utökad händelse för detaljerad administratörsrelation ("granular-admin-relationship-auto-extended")

    Den här händelsen utlöses när systemet automatiskt utökar de detaljerade delegerade administratörsbehörigheterna.

  • Detaljerad händelse för avslutad administratörsrelation ("granular-admin-relationship-terminated")

    Den här händelsen utlöses när partner- eller kundklientorganisationen avslutar de detaljerade delegerade administratörsbehörigheterna.

  • Fakturaklar händelse ("fakturaklar")

    Den här händelsen utlöses när den nya fakturan är klar.

  • Ny handelsmigrering har slutförts ("new-commerce-migration-completed")

    Den här händelsen utlöses när den nya handelsmigreringen har slutförts.

  • Ny handelsmigrering har skapats ("new-commerce-migration-created")

    Den här händelsen utlöses när den nya handelsmigreringen skapas.

  • Ny handelsmigrering misslyckades ("new-commerce-migration-failed")

    Den här händelsen utlöses när den nya handelsmigreringen misslyckas.

  • Skapa överföring ("create-transfer")

    Den här händelsen utlöses när överföringen skapas.

  • Uppdateringsöverföring ("update-transfer")

    Den här händelsen utlöses när överföringen uppdateras.

  • Fullständig överföring ("complete-transfer")

    Den här händelsen utlöses när överföringen har slutförts.

  • Misslyckad överföring ("fail-transfer")

    Den här händelsen utlöses när överföringen misslyckas.

  • Nytt handelsmigreringsschema misslyckades ("new-commerce-migration-schedule-failed")

    Den här händelsen utlöses när det nya handelsmigreringsschemat misslyckas.

  • Händelse som skapats av hänvisning ("referral-created")

    Den här händelsen utlöses när hänvisningen skapas.

  • Uppdaterad händelse för hänvisning ("referral-updated")

    Den här händelsen utlöses när hänvisningen uppdateras.

  • Relaterad referens skapad händelse ("related-referral-created")

    Den här händelsen utlöses när den relaterade hänvisningen skapas.

  • Relaterad referens uppdaterad händelse ("related-referral-updated")

    Den här händelsen utlöses när den relaterade hänvisningen uppdateras.

  • Aktiv prenumerationshändelse ("prenumerationsaktiv")

    Den här händelsen utlöses när prenumerationen aktiveras.

    Kommentar

    Webhooken Prenumerationsaktiv och motsvarande aktivitetslogghändelse är endast tillgängliga för Sandbox-klientorganisationer just nu.

  • Väntande händelse för prenumeration ("prenumeration väntar")

    Den här händelsen utlöses när motsvarande order har tagits emot på ett lyckat sätt och prenumerationsskapandet väntar.

    Kommentar

    Webhooken Prenumeration väntar och motsvarande aktivitetslogghändelse är endast tillgänglig för Sandbox-klientorganisationer just nu.

  • Förnyad prenumerationshändelse ("prenumeration förnyad")

    Den här händelsen utlöses när prenumerationen slutför förnyelsen.

    Kommentar

    Webhooken Förnyad prenumeration och motsvarande aktivitetslogghändelse är endast tillgänglig för Sandbox-klientorganisationer just nu..

  • Uppdaterad händelse för prenumeration ("prenumerationen har uppdaterats")

    Den här händelsen utlöses när prenumerationen ändras. Dessa händelser genereras när det sker en intern ändring utöver när ändringar görs via Partnercenter-API:et.

    Kommentar

    Det finns en fördröjning på upp till 48 timmar mellan tiden då en prenumeration ändras och när händelsen Prenumerationsuppdaterad utlöses.

  • Testhändelse ("testskapad")

    Med den här händelsen kan du registrera dig själv och testa registreringen genom att begära en testhändelse och sedan spåra dess förlopp. Du kan se de felmeddelanden som tas emot från Microsoft när du försöker leverera händelsen. Den här begränsningen gäller endast för "testskapade" händelser. Data som är äldre än sju dagar rensas.

  • Tröskelvärdet överskred händelsen ("usagerecords-thresholdExceeded")

    Den här händelsen utlöses när mängden Microsoft Azure-användning för alla kunder överskrider deras användningsbudget (deras tröskelvärde). Mer information finns i (Ange en Azure-utgiftsbudget för dina kunder/partnercenter/set-an-azure-spending-budget-for-your-customers).

Framtida Webhook-händelser läggs till för resurser som ändras i systemet som partnern inte har kontroll över, och ytterligare uppdateringar kommer att göras för att få dessa händelser så nära "realtid" som möjligt. Feedback från partner om vilka händelser som tillför värde till verksamheten är användbart för att avgöra vilka nya händelser som ska läggas till.

En fullständig lista över Webhook-händelser som stöds av Partnercenter finns i Webhook-händelser i Partnercenter.

Förutsättningar

  • Autentiseringsuppgifter enligt beskrivningen i Partnercenter-autentisering. Det här scenariot stöder autentisering med både fristående app- och App+User-autentiseringsuppgifter.

Ta emot händelser från Partnercenter

Om du vill ta emot händelser från Partnercenter måste du exponera en offentligt tillgänglig slutpunkt. Eftersom den här slutpunkten exponeras måste du verifiera att kommunikationen kommer från Partnercenter. Alla Webhook-händelser som du får är digitalt signerade med ett certifikat som kedjar till Microsoft Root. En länk till certifikatet som används för att signera händelsen finns också. På så sätt kan certifikatet förnyas utan att du behöver distribuera om eller konfigurera om tjänsten. Partnercenter gör 10 försök att leverera händelsen. Om händelsen fortfarande inte levereras efter 10 försök flyttas den till en offlinekö och inga ytterligare försök görs vid leveransen.

Följande exempel visar en händelse som publicerats från Partnercenter.

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

Kommentar

Auktoriseringshuvudet har ett schema med "Signatur". Det här är en base64-kodad signatur för innehållet.

Så här autentiserar du återanropet

Följ dessa steg för att autentisera motringningshändelsen som tagits emot från Partnercenter:

  1. Kontrollera att de nödvändiga rubrikerna finns (auktorisering, x-ms-certificate-url, x-ms-signature-algorithm).
  2. Ladda ned certifikatet som används för att signera innehållet (x-ms-certificate-url).
  3. Verifiera certifikatkedjan.
  4. Verifiera certifikatets "organisation".
  5. Läs innehållet med UTF8-kodning i en buffert.
  6. Skapa en RSA-kryptoprovider.
  7. Kontrollera att data matchar det som signerades med den angivna hash-algoritmen (till exempel SHA256).
  8. Om verifieringen lyckas bearbetar du meddelandet.

Kommentar

Som standard skickas signaturtoken i ett auktoriseringshuvud. Om du anger SignatureTokenToMsSignatureHeader till true i din registrering skickas signaturtoken i rubriken x-ms-signature i stället.

Händelsemodell

I följande tabell beskrivs egenskaperna för en PartnerCenter-händelse.

Egenskaper

Name beskrivning
EventName Namnet på händelsen. I formuläret {resource}-{action}. Till exempel "test-created".
ResourceUri URI:n för resursen som ändrades.
ResourceName Namnet på resursen som ändrades.
AuditUrl Valfritt. URI:n för granskningsposten.
ResourceChangeUtcDate Datum och tid, i UTC-format, när resursändringen inträffade.

Exempel

Följande exempel visar strukturen för en PartnerCenter-händelse.

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

Autentisering

Alla anrop till Webhook-API:erna autentiseras med hjälp av ägartoken i auktoriseringshuvudet. Hämta en åtkomsttoken för att få åtkomst till https://api.partnercenter.microsoft.com. Den här token är samma token som används för att komma åt resten av Partnercenter-API:erna.

Hämta en lista över händelser

Returnerar en lista över de händelser som för närvarande stöds av Webhook-API:erna.

Resurs-URL

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

Exempel på begäran

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

Svarsexempel

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

Registrera dig för att ta emot händelser

Registrerar en klientorganisation för att ta emot de angivna händelserna.

Resurs-URL

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

Exempel på begäran

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

Svarsexempel

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

Visa en registrering

Returnerar webhooks-händelseregistreringen för en klientorganisation.

Resurs-URL

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

Exempel på begäran

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

Svarsexempel

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

Uppdatera en händelseregistrering

Uppdaterar en befintlig händelseregistrering.

Resurs-URL

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

Exempel på begäran

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

Svarsexempel

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

Skicka en testhändelse för att verifiera din registrering

Genererar en testhändelse för att verifiera Webhooks-registreringen. Det här testet är avsett att verifiera att du kan ta emot händelser från Partnercenter. Data för dessa händelser tas bort sju dagar efter att den första händelsen har skapats. Du måste vara registrerad för händelsen "testskapad" med hjälp av registrerings-API:et innan du skickar en valideringshändelse.

Kommentar

Det finns en begränsningsgräns på 2 begäranden per minut när du publicerar en valideringshändelse.

Resurs-URL

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

Exempel på begäran

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:

Svarsexempel

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

Kontrollera att händelsen levererades

Returnerar valideringshändelsens aktuella tillstånd. Den här verifieringen kan vara till hjälp vid felsökning av problem med händelseleverans. Svaret innehåller ett resultat för varje försök att leverera händelsen.

Resurs-URL

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

Exempel på begäran

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

Svarsexempel

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

Exempel på signaturverifiering

Exempel på återanropsstyrenhetssignatur (ASP.NET)

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

Signaturverifiering

I följande exempel visas hur du lägger till ett auktoriseringsattribut till den kontrollant som tar emot återanrop från Webhook-händelser.

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