Elementy webhook Centrum partnerskiego

Dotyczy: Centrum partnerskie | Centrum partnerskie obsługiwane przez firmę 21Vianet | Centrum partnerskie dla chmury firmy Microsoft dla instytucji rządowych USA

Odpowiednie role: administrator globalny | Administracja rozliczeń | agent Administracja | Agent sprzedaży | Agent pomocy technicznej

Interfejsy API elementu webhook Centrum partnerskiego umożliwiają partnerom rejestrowanie się na potrzeby zdarzeń zmiany zasobów. Te zdarzenia są dostarczane w postaci poST HTTP do zarejestrowanego adresu URL partnera. Aby odebrać zdarzenie z Centrum partnerskiego, partnerzy będą hostować wywołanie zwrotne, w którym Centrum partnerskie może opublikować zdarzenie zmiany zasobu. Wydarzenie zostanie podpisane cyfrowo, aby partner mógł sprawdzić, czy został wysłany z Centrum partnerskiego. Powiadomienia elementu webhook są wyzwalane tylko w środowisku, które ma najnowszą konfigurację wspólnej sprzedaży.

Partnerzy mogą wybierać spośród zdarzeń elementu webhook, takich jak poniższe przykłady, które są obsługiwane przez Centrum partnerskie.

  • Wykryto zdarzenie oszustwa platformy Azure ("azure-fraud-event-detected")

    To zdarzenie jest zgłaszane po wykryciu zdarzenia oszustwa platformy Azure.

  • Zdarzenie zatwierdzonej relacji delegowanej Administracja ("dap-admin-relationship-approved")

    To zdarzenie jest zgłaszane, gdy delegowane uprawnienia Administracja zostały zatwierdzone przez dzierżawę klienta.

  • Relacja odsprzedawcy zaakceptowana przez zdarzenie klienta ("reseller-relationship-accepted-by-customer")

    To zdarzenie jest zgłaszane, gdy relacja odsprzedawcy jest zatwierdzana przez dzierżawę klienta.

  • Zdarzenie zakończonej relacji delegowanej Administracja ("dap-admin-relationship-terminated")

    To zdarzenie jest zgłaszane, gdy delegowane uprawnienia Administracja zostały zakończone przez klienta.

  • Relacja dap Administracja zakończona przez zdarzenie firmy Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    To zdarzenie jest zgłaszane, gdy firma Microsoft kończy obsługę języka DAP między dzierżawą Partner i Klient, gdy usługa DAP jest nieaktywna przez ponad 90 dni.

  • Zdarzenie aktywowane szczegółowego przypisania dostępu Administracja ("granular-admin-access-assignment-activated")

    To zdarzenie jest zgłaszane, gdy przypisanie dostępu do uprawnień delegowanych Administracja szczegółowe jest aktywowane przez partnera po przypisaniu ról firmy Microsoft Entra do określonych grup zabezpieczeń.

  • Szczegółowe zdarzenie tworzenia przypisania dostępu Administracja ("granular-admin-access-assignment-created")

    To zdarzenie jest zgłaszane po utworzeniu przez partnera przypisania dostępu delegowanego Administracja uprawnień szczegółowego. Partnerzy mogą przypisywać zatwierdzone przez klienta role firmy Microsoft do określonych grup zabezpieczeń.

  • Szczegółowe zdarzenie usunięcia przypisania dostępu Administracja ("szczegółowe-administrator-access-assignment-deleted")

    To zdarzenie jest zgłaszane, gdy partner usunął przypisanie dostępu delegowanego Administracja uprawnień.

  • Szczegółowe zdarzenie aktualizacji przypisania dostępu Administracja ("granular-admin-access-assignment-updated")

    To zdarzenie jest zgłaszane, gdy partner zaktualizował przypisanie dostępu delegowanego Administracja uprawnień szczegółowego.

  • Zdarzenie aktywowane szczegółowej relacji Administracja ("szczegółowe aktywowane relacje administratora")

    To zdarzenie jest wywoływane, gdy zostanie utworzone szczegółowe uprawnienia delegowane Administracja i aktywne dla klienta do zatwierdzenia.

  • Szczegółowe zdarzenie zatwierdzonej relacji Administracja ("szczegółowe zatwierdzenie relacji administratora")

    To zdarzenie jest zgłaszane, gdy dzierżawa klienta zatwierdziła delegowane uprawnienia Administracja szczegółowe.

  • Szczegółowe zdarzenie wygaśnięcia relacji Administracja ("szczegółowe-admin-relationship-expired")

    To zdarzenie jest zgłaszane po wygaśnięciu uprawnień delegowanych Administracja szczegółowych.

  • Szczegółowe zdarzenie aktualizacji relacji Administracja ("szczegółowe aktualizacje relacji administratora")

    To zdarzenie jest zgłaszane, gdy uprawnienia delegowane szczegółowe Administracja są aktualizowane przez dzierżawę partnera/klienta.

  • Szczegółowe zdarzenie rozszerzonej relacji Administracja ("szczegółowe-admin-relationship-auto-extended")

    To zdarzenie jest zgłaszane, gdy system automatycznie rozszerza szczegółowe uprawnienia delegowane Administracja.

  • Szczegółowe zdarzenie zakończonej relacji Administracja ("szczegółowe zakończenie relacji administratora")

    To zdarzenie jest zgłaszane, gdy uprawnienia delegowane szczegółowe Administracja są kończone przez dzierżawę partnera/klienta.

  • Zakończono migrację nowego handlu ("new-commerce-migration-completed")

    To zdarzenie jest zgłaszane po zakończeniu migracji do nowego handlu.

  • Utworzono nową migrację handlową ("new-commerce-migration-created")

    To zdarzenie jest zgłaszane podczas tworzenia nowej migracji handlowej.

  • Migracja nowego handlu nie powiodła się ("new-commerce-migration-failed")

    To zdarzenie jest zgłaszane, gdy migracja nowego handlu nie powiedzie się.

  • Nowy harmonogram migracji do handlu nie powiódł się ("new-commerce-migration-schedule-failed")

    To zdarzenie jest zgłaszane, gdy nowy harmonogram migracji handlu nie powiedzie się.

  • Zdarzenie utworzone w odwołaniu ("utworzone odwołanie")

    To zdarzenie jest zgłaszane podczas tworzenia odwołania.

  • Zdarzenie zaktualizowane odwołania ("zaktualizowano odwołania")

    To zdarzenie jest zgłaszane po zaktualizowaniu odwołania.

  • Powiązane zdarzenie utworzone polecenia ("related-referral-created")

    To zdarzenie jest zgłaszane podczas tworzenia powiązanego odwołania.

  • Powiązane zdarzenie aktualizacji odwołań ("related-referral-updated")

    To zdarzenie jest zgłaszane po zaktualizowaniu powiązanego odwołania.

  • Zdarzenie zaktualizowane subskrypcji ("subskrypcja zaktualizowana")

    To zdarzenie jest zgłaszane, gdy subskrypcja ulegnie zmianie. Te zdarzenia będą generowane w przypadku wystąpienia wewnętrznych zmian poza wprowadzeniem zmian za pośrednictwem interfejsu API Centrum partnerskiego.

    Uwaga

    Między upływem czasu zmiany subskrypcji a wyzwoleniem zdarzenia Aktualizacja subskrypcji trwa do 48 godzin.

  • Zdarzenie testowe ("utworzone test")

    To zdarzenie umożliwia samodzielne dołączanie i testowanie rejestracji przez żądanie zdarzenia testowego, a następnie śledzenie postępu. Podczas próby dostarczenia zdarzenia można zobaczyć komunikaty o błędach odbierane od firmy Microsoft. To ograniczenie dotyczy tylko zdarzeń "utworzonych przez test". Dane starsze niż siedem dni zostaną przeczyszczone.

  • Przekroczono zdarzenie progowe ("usagerecords-thresholdExceeded")

    To zdarzenie jest zgłaszane, gdy kwota użycia platformy Microsoft Azure dla każdego klienta przekracza budżet wydatków na użycie (ich próg). Aby uzyskać więcej informacji, zobacz (Ustawianie budżetu wydatków na platformę Azure dla klientów/centrum partnerskiego/set-an-azure-spending-budget-for-your-customers).

Przyszłe zdarzenia elementu webhook zostaną dodane dla zasobów, które zmieniają się w systemie, którego partner nie kontroluje, a dalsze aktualizacje zostaną wprowadzone w celu uzyskania tych zdarzeń tak blisko "czasu rzeczywistego", jak to możliwe. Opinie od partnerów, które zdarzenia dodają wartość do swojej firmy, będą przydatne podczas określania, jakie nowe zdarzenia mają zostać dodane.

Aby uzyskać pełną listę zdarzeń elementu webhook obsługiwanych przez Centrum partnerskie, zobacz Zdarzenia elementu webhook w Centrum partnerskim.

Wymagania wstępne

  • Poświadczenia zgodnie z opisem w temacie Uwierzytelnianie w Centrum partnerskim. Ten scenariusz obsługuje uwierzytelnianie zarówno przy użyciu autonomicznych poświadczeń aplikacji, jak i aplikacji i użytkownika.

Odbieranie zdarzeń z Centrum partnerskiego

Aby odbierać zdarzenia z Centrum partnerskiego, musisz udostępnić publicznie dostępny punkt końcowy. Ponieważ ten punkt końcowy jest uwidoczniony, należy sprawdzić, czy komunikacja pochodzi z Centrum partnerskiego. Wszystkie otrzymane zdarzenia elementu webhook są podpisane cyfrowo przy użyciu certyfikatu, który jest łańcuchem do katalogu głównego firmy Microsoft. Zostanie również udostępniony link do certyfikatu użytego do podpisania zdarzenia. Umożliwi to odnowienie certyfikatu bez konieczności ponownego wdrażania lub ponownego konfigurowania usługi. Centrum partnerskie podejmie 10 prób dostarczenia zdarzenia. Jeśli zdarzenie nie zostanie jeszcze dostarczone po 10 próbach, zostanie przeniesione do kolejki offline i nie zostaną wykonane dalsze próby dostarczenia.

Poniższy przykład przedstawia zdarzenie opublikowane w Centrum partnerskim.

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

Uwaga

Nagłówek Autoryzacja ma schemat "Signature". Jest to zakodowany w formacie base64 podpis zawartości.

Jak uwierzytelnić wywołanie zwrotne

Aby uwierzytelnić zdarzenie wywołania zwrotnego odebrane z Centrum partnerskiego, wykonaj następujące kroki:

  1. Sprawdź, czy istnieją wymagane nagłówki (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Pobierz certyfikat użyty do podpisania zawartości (x-ms-certificate-url).

  3. Sprawdź łańcuch certyfikatów.

  4. Sprawdź wartość "Organizacja" certyfikatu.

  5. Odczytywanie zawartości z kodowaniem UTF8 w buforze.

  6. Utwórz dostawcę kryptograficznego RSA.

  7. Sprawdź, czy dane pasują do tego, co zostało podpisane przy użyciu określonego algorytmu wyznaczania wartości skrótu (na przykład SHA256).

  8. Jeśli weryfikacja zakończy się pomyślnie, przetwórz komunikat.

Uwaga

Domyślnie token podpisu zostanie wysłany w nagłówku autoryzacji. W przypadku ustawienia parametru SignatureTokenToMsSignatureHeader na wartość true w rejestracji token podpisu zostanie wysłany w nagłówku x-ms-signature.

Model zdarzeń

W poniższej tabeli opisano właściwości zdarzenia Centrum partnerskiego.

Właściwości

Nazwa/nazwisko opis
Eventname Nazwa zdarzenia. W formularzu {resource}-{action}. Na przykład "test-created".
Identyfikator ResourceUri Identyfikator URI zmienionego zasobu.
Resourcename Nazwa zmienionego zasobu.
AuditUrl Opcjonalny. Identyfikator URI rekordu inspekcji.
ResourceChangeUtcDate Data i godzina w formacie UTC, kiedy nastąpiła zmiana zasobu.

Przykład

Poniższy przykład przedstawia strukturę zdarzenia Centrum partnerskiego.

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

Interfejsy API elementów webhook

Uwierzytelnianie

Wszystkie wywołania interfejsów API elementu webhook są uwierzytelniane przy użyciu tokenu elementu nośnego w nagłówku autoryzacji. Uzyskiwanie tokenu dostępu w celu uzyskania dostępu do https://api.partnercenter.microsoft.comelementu . Ten token jest tym samym tokenem, który jest używany do uzyskiwania dostępu do pozostałych interfejsów API Centrum partnerskiego.

Pobieranie listy zdarzeń

Zwraca listę zdarzeń, które są obecnie obsługiwane przez interfejsy API elementu webhook.

Adres URL zasobu

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

Przykład żądania

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

Przykład odpowiedzi

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

Rejestrowanie w celu odbierania zdarzeń

Rejestruje dzierżawę w celu odbierania określonych zdarzeń.

Adres URL zasobu

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

Przykład żądania

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

Przykład odpowiedzi

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

Wyświetlanie rejestracji

Zwraca rejestrację zdarzeń elementów webhook dla dzierżawy.

Adres URL zasobu

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

Przykład żądania

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

Przykład odpowiedzi

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

Aktualizowanie rejestracji zdarzeń

Aktualizacje istniejącej rejestracji zdarzeń.

Adres URL zasobu

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

Przykład żądania

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

Przykład odpowiedzi

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

Wysyłanie zdarzenia testowego w celu zweryfikowania rejestracji

Generuje zdarzenie testowe w celu zweryfikowania rejestracji elementów webhook. Ten test ma na celu sprawdzenie, czy zdarzenia można odbierać z Centrum partnerskiego. Dane dotyczące tych zdarzeń zostaną usunięte siedem dni po utworzeniu zdarzenia początkowego. Przed wysłaniem zdarzenia weryfikacji należy zarejestrować zdarzenie "utworzone przez test" przy użyciu interfejsu API rejestracji.

Uwaga

Podczas publikowania zdarzenia weryfikacji występuje limit ograniczenia 2 żądań na minutę.

Adres URL zasobu

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

Przykład żądania

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:

Przykład odpowiedzi

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

Sprawdź, czy zdarzenie zostało dostarczone

Zwraca bieżący stan zdarzenia weryfikacji. Ta weryfikacja może być przydatna do rozwiązywania problemów z dostarczaniem zdarzeń. Odpowiedź zawiera wynik dla każdej próby dostarczenia zdarzenia.

Adres URL zasobu

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

Przykład żądania

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

Przykład odpowiedzi

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

Przykład weryfikacji podpisu

Przykładowy podpis kontrolera wywołania zwrotnego (ASP.NET)

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

Walidacja podpisu

W poniższym przykładzie pokazano, jak dodać atrybut autoryzacji do kontrolera, który odbiera wywołania zwrotne z zdarzeń elementu webhook.

namespace Webhooks.Security
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Http;
    using System.Web.Http.Controllers;
    using Microsoft.Partner.Logging;

    /// <summary>
    /// Signature based Authorization
    /// </summary>
    public class AuthorizeSignatureAttribute : AuthorizeAttribute
    {
        private const string MsSignatureHeader = "x-ms-signature";
        private const string CertificateUrlHeader = "x-ms-certificate-url";
        private const string SignatureAlgorithmHeader = "x-ms-signature-algorithm";
        private const string MicrosoftCorporationIssuer = "O=Microsoft Corporation";
        private const string SignatureScheme = "Signature";

        /// <inheritdoc/>
        public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            ValidateAuthorizationHeaders(actionContext.Request);

            await VerifySignature(actionContext.Request);
        }

        private static async Task<string> GetContentAsync(HttpRequestMessage request)
        {
            // By default the stream can only be read once and we need to read it here so that we can hash the body to validate the signature from microsoft.
            // Load into a buffer, so that the stream can be accessed here and in the api when it binds the content to the expected model type.
            await request.Content.LoadIntoBufferAsync();

            var s = await request.Content.ReadAsStreamAsync();
            var reader = new StreamReader(s);
            var body = await reader.ReadToEndAsync();

            // set the stream position back to the beginning
            if (s.CanSeek)
            {
                s.Seek(0, SeekOrigin.Begin);
            }

            return body;
        }

        private static void ValidateAuthorizationHeaders(HttpRequestMessage request)
        {
            var authHeader = request.Headers.Authorization;
            if (string.IsNullOrWhiteSpace(authHeader?.Parameter) && string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, MsSignatureHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Authorization header missing."));
            }

            var signatureHeaderValue = GetHeaderValue(request.Headers, MsSignatureHeader);
            if (authHeader != null
                && !string.Equals(authHeader.Scheme, SignatureScheme, StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrWhiteSpace(signatureHeaderValue)
                && !signatureHeaderValue.StartsWith(SignatureScheme, StringComparison.OrdinalIgnoreCase))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Authorization scheme needs to be '{SignatureScheme}'."));
            }

            if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, CertificateUrlHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {CertificateUrlHeader} missing."));
            }

            if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, SignatureAlgorithmHeader)))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {SignatureAlgorithmHeader} missing."));
            }
        }

        private static string GetHeaderValue(HttpHeaders headers, string key)
        {
            headers.TryGetValues(key, out var headerValues);

            return headerValues?.FirstOrDefault();
        }

        private static async Task VerifySignature(HttpRequestMessage request)
        {
            // Get signature value from either authorization header or x-ms-signature header.
            var base64Signature = request.Headers.Authorization?.Parameter ?? GetHeaderValue(request.Headers, MsSignatureHeader).Split(' ')[1];
            var signatureAlgorithm = GetHeaderValue(request.Headers, SignatureAlgorithmHeader);
            var certificateUrl = GetHeaderValue(request.Headers, CertificateUrlHeader);
            var certificate = await GetCertificate(certificateUrl);
            var content = await GetContentAsync(request);
            var alg = signatureAlgorithm.Split('-'); // for example RSA-SHA1
            var isValid = false;

            var logger = GetLoggerIfAvailable(request);

            // Validate the certificate
            VerifyCertificate(certificate, request, logger);

            if (alg.Length == 2 && alg[0].Equals("RSA", StringComparison.OrdinalIgnoreCase))
            {
                var signature = Convert.FromBase64String(base64Signature);
                var csp = (RSACryptoServiceProvider)certificate.PublicKey.Key;

                var encoding = new UTF8Encoding();
                var data = encoding.GetBytes(content);

                var hashAlgorithm = alg[1].ToUpper();

                isValid = csp.VerifyData(data, CryptoConfig.MapNameToOID(hashAlgorithm), signature);
            }

            if (!isValid)
            {
                // log that we were not able to validate the signature
                logger?.TrackTrace(
                    "Failed to validate signature for webhook callback",
                    new Dictionary<string, string> { { "base64Signature", base64Signature }, { "certificateUrl", certificateUrl }, { "signatureAlgorithm", signatureAlgorithm }, { "content", content } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Signature verification failed"));
            }
        }

        private static ILogger GetLoggerIfAvailable(HttpRequestMessage request)
        {
            return request.GetDependencyScope().GetService(typeof(ILogger)) as ILogger;
        }

        private static async Task<X509Certificate2> GetCertificate(string certificateUrl)
        {
            byte[] certBytes;
            using (var webClient = new WebClient())
            {
                certBytes = await webClient.DownloadDataTaskAsync(certificateUrl);
            }

            return new X509Certificate2(certBytes);
        }

        private static void VerifyCertificate(X509Certificate2 certificate, HttpRequestMessage request, ILogger logger)
        {
            if (!certificate.Verify())
            {
                logger?.TrackTrace("Failed to verify certificate for webhook callback.", new Dictionary<string, string> { { "Subject", certificate.Subject }, { "Issuer", certificate.Issuer } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Certificate verification failed."));
            }

            if (!certificate.Issuer.Contains(MicrosoftCorporationIssuer))
            {
                logger?.TrackTrace($"Certificate not issued by {MicrosoftCorporationIssuer}.", new Dictionary<string, string> { { "Issuer", certificate.Issuer } });

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Certificate not issued by {MicrosoftCorporationIssuer}."));
            }
        }
    }
}