Udostępnij za pośrednictwem


Webhooki 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 rozliczeń | Agent administracyjny | Agent sprzedaży | Agent pomocy technicznej

Webhook API Centrum Partnerów umożliwiają partnerom rejestrowanie się na zdarzenia związane ze zmianą zasobów. Te zdarzenia są dostarczane w postaci poST HTTP do zarejestrowanego adresu URL partnera. Aby odebrać zdarzenie z Centrum partnerskiego, partnerzy hostują wywołanie zwrotne, w którym Centrum partnerskie może opublikować zdarzenie zmiany zasobu. Zdarzenie jest podpisane cyfrowo, aby partner mógł sprawdzić, czy został wysłany z Centrum partnerskiego. Powiadomienia webhook są uruchamiane tylko w środowisku, które ma najnowszą konfigurację Co-sell.

Partner Center obsługuje następujące zdarzenia Webhook.

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

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

  • Zdarzenie zatwierdzonej relacji administratora delegowanego ("dap-admin-relationship-approved")

    Zdarzenie to jest wyzwalane, gdy uprawnienia delegowanego administratora są zatwierdzane przez dzierżawcę klienta.

  • Zaakceptowanie relacji z odsprzedawcą przez klienta ("reseller-relationship-accepted-by-customer")

    To zdarzenie jest zgłaszane, gdy klient będący najemcą zatwierdza relację z odsprzedawcą.

  • Zdarzenie zaakceptowania przez klienta relacji z pośrednim odsprzedawcą ("indirect-reseller-relationship-accepted-by-customer")

    Zdarzenie to jest inicjowane, gdy klient zatwierdza relację z pośrednim odsprzedawcą.

  • Zdarzenie zakończenia relacji administratora delegowanego ("dap-admin-relationship-terminated")

    To zdarzenie jest zgłaszane, gdy klient zakończy uprawnienia administratora delegowanego.

  • Relacja Administratora DAP zakończona przez wydarzenie Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    To zdarzenie jest wywoływane, gdy Microsoft przerywa DAP między tenantem Partnera i Klienta, gdy DAP jest nieaktywny przez ponad 90 dni.

  • Zdarzenie aktywowania granulowanego przypisania dostępu administratora ("granular-admin-access-assignment-activated")

    To zdarzenie jest zgłaszane, gdy partner aktywuje przypisanie dostępu dla szczegółowych uprawnień administratora delegowanego, po przypisaniu ról Microsoft Entra do określonych grup zabezpieczeń.

  • Zdarzenie utworzenia przypisania dostępu administratora o granularnym poziomie ("granular-admin-access-assignment-created")

    To zdarzenie jest zgłaszane, gdy partner dokonuje przypisania dostępu do szczegółowych delegowanych uprawnień administratora. Partnerzy mogą przypisywać role Microsoft Entra zatwierdzone przez klienta do określonych grup bezpieczeństwa.

  • Granularne zdarzenie usunięcia przypisania dostępu administratora ("granularne-usunięcie-przypisania-dostępu-administratora")

    To zdarzenie jest zgłaszane, gdy partner usunie przypisanie dostępu do uprawnień administratora delegowanego granularnego.

  • Zaktualizowane zdarzenie szczegółowego przydzielenia dostępu administratora ("szczegółowe-przydzielenie-dostępu-administratora-zaktualizowano")

    To zdarzenie jest zgłaszane, gdy partner aktualizuje przypisanie dostępu do szczegółowych delegowanych uprawnień administratora.

  • Zdarzenie aktywacji szczegółowego poziomu uprawnień administratora ("aktywacja relacji szczegółowego administratora")

    To zdarzenie jest zgłaszane, gdy Granularne Delegowane Uprawnienia Administratora są tworzone i stają się aktywne, oczekując na zatwierdzenie przez klienta.

  • Wydarzenie zatwierdzenia relacji administratora z granularnymi uprawnieniami ("granular-admin-relationship-approved")

    To zdarzenie jest wywoływane, gdy tenant klienta zatwierdza granularne uprawnienia delegowanego administratora.

  • Zdarzenie wygaśnięcia granularnej relacji administratora ("wygaśnięcie granularnej relacji administratora")

    Zdarzenie to występuje, gdy wygasają granularne delegowane uprawnienia administratora.

  • Zdarzenie utworzenia szczegółowej relacji administratora ("utworzenie-szczegółowej-relacji-administratora")

    To zdarzenie jest zgłaszane podczas tworzenia szczegółowych uprawnień administratora delegowanego.

  • Zaktualizowane zdarzenie szczegółowej relacji administratora ("szczegółowe informacje dotyczące relacji administratora")

    To zdarzenie jest inicjowane, gdy klient lub partner zaktualizuje granularne uprawnienia administratora delegowanego.

  • Zdarzenie automatycznego rozszerzenia relacji granularnej ("granular-admin-relationship-auto-extended")

    To zdarzenie jest wyzwalane, gdy system automatycznie rozszerza drobiazgowe przywileje delegowane administratora.

  • Zdarzenie granularnego zakończenia relacji administratora ("granularne zakończenie relacji administratora")

    To zdarzenie jest wywoływane, gdy partner lub klient anuluje szczegółowe uprawnienia delegowane dla administratora.

  • Zakończono migrację New Commerce ("new-commerce-migration-completed")

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

  • Nowa migracja handlowa została utworzona ("new-commerce-migration-created")

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

  • Migracja na nową platformę handlową nie powiodła się ("new-commerce-migration-failed")

    Zdarzenie to jest zgłaszane, gdy migracja nowego systemu handlowego nie powiedzie się.

  • Tworzenie transferu ("create-transfer")

    To zdarzenie jest zgłaszane w momencie tworzenia transferu.

  • Aktualizacja transferu ("update-transfer")

    To zdarzenie jest zgłaszane, gdy transfer zostaje zaktualizowany.

  • Kompletne Przeniesienie ("complete-transfer")

    To zdarzenie jest zgłaszane po zakończeniu transferu.

  • Wygasanie transferu ("expire-transfer")

    To zdarzenie jest zgłaszane po wygaśnięciu transferu.

  • Nieudany transfer ("fail-transfer")

    To zdarzenie jest zgłaszane, gdy transfer zakończy się niepowodzeniem.

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

    To zdarzenie jest zgłaszane, gdy nowego harmonogramu migracji handlu nie uda się wykonać.

  • Zdarzenie utworzenia rekomendacji ("utworzenie-rekomendacji")

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

  • Zdarzenie aktualizacji rekomendacji ("aktualizacja-rekomendacji")

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

  • Zdarzenie utworzenia powiązanego odesłania ("related-referral-created")

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

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

    Zdarzenie to jest generowane, gdy powiązane skierowanie zostanie zaktualizowane.

  • Zdarzenie aktywacji subskrypcji ("aktywna subskrypcja")

    To zdarzenie jest zgłaszane po aktywowaniu subskrypcji.

    Uwaga

    Webhook Aktywnej Subskrypcji i odpowiadające mu Zdarzenie Dziennika Aktywności są w tej chwili dostępne tylko dla klientów środowiska testowego.

  • Zdarzenie oczekujące na subskrypcję ("oczekiwanie na subskrypcję")

    To zdarzenie jest zgłaszane, gdy odpowiednie zamówienie zostało pomyślnie przesłane, a tworzenie subskrypcji jest w toku.

    Uwaga

    Webhook Oczekującej Subskrypcji oraz odpowiadające zdarzenie w dzienniku aktywności są obecnie dostępne tylko dla Sandbox tenantów.

  • Zdarzenie odnowienia subskrypcji ("odnowienie subskrypcji")

    To zdarzenie jest zgłaszane po zakończeniu odnawiania subskrypcji.

    Uwaga

    Webhook odnawiania subskrypcji i odpowiadające mu zdarzenie logu aktywności są obecnie dostępne tylko dla środowisk piaskownicy.

  • Zdarzenie zaktualizowania subskrypcji ("subskrypcja-zaktualizowana")

    To zdarzenie jest zgłaszane, gdy subskrypcja ulegnie zmianie. Te zdarzenia są generowane, gdy oprócz zmian wprowadzonych za pośrednictwem interfejsu API Centrum partnerskiego następuje zmiana wewnętrzna.

    Uwaga

    Opóźnienie między zmianą subskrypcji a wyzwoleniem zdarzenia o nazwie Aktualizacja subskrypcji wynosi do 48 godzin.

  • Zdarzenie testowe ("test-created")

    To wydarzenie umożliwia samodzielne zarejestrowanie się i testowanie swojej rejestracji poprzez zażądanie testowego wydarzenia i śledzenie jego postępów. 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 są czyszczone.

  • Zdarzenie przekroczenia progu ("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 (Ustaw budżet wydatków na platformę Azure dla klientów/centrum partnerskiego/ustaw-budzet-wydatkow-na-platforme-azure-dla-klientow).

W przyszłości zostaną dodane zdarzenia webhook dla zasobów, które zmieniają się w systemie, nad którym partner nie ma kontroli. Dalsze aktualizacje będą wprowadzane, aby te zdarzenia były jak najbliższe "czasowi rzeczywistemu". Opinie partnerów na temat tego, które zdarzenia dodają wartość do swojej firmy, są przydatne podczas określania nowych zdarzeń do dodania.

Aby uzyskać pełną listę zdarzeń webhook obsługiwanych przez Centrum partnerskie, zobacz Zdarzenia 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 samodzielnych poświadczeń aplikacji, jak i poświadczeń 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 zdarzenia Webhook, które otrzymujesz, są podpisane cyfrowo przy użyciu certyfikatu, który jest związany z Głównym Certyfikatem Microsoft. Również zostanie udostępniony link do certyfikatu użytego do podpisania zdarzenia. Dzięki temu certyfikat może zostać odnowiony bez konieczności ponownego wdrażania lub ponownego konfigurowania usługi. Centrum Partnerów podejmuje 10 prób przekazania zdarzenia. Jeśli zdarzenie nie zostanie dostarczone po 10 próbach, zostanie przeniesione do kolejki offline i nie będą podejmowane 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 Authorization 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ź "Organizację" certyfikatu.
  5. Odczytaj zawartość z kodowaniem UTF8 do bufora.
  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 wiadomość.

Uwaga

Domyślnie token podpisu jest wysyłany w nagłówku autoryzacji. Jeśli ustawisz parametr SignatureTokenToMsSignatureHeader na wartość true w ustawieniach rejestracji, token podpisu zostanie wysłany w nagłówku x-ms-signature.

Model zdarzeń

W poniższej tabeli opisano właściwości wydarzenia Partner Center.

Właściwości

Nazwa/nazwisko opis
EventName Nazwa zdarzenia. W formacie {resource}-{action}. Na przykład "test-created".
ResourceUri Identyfikator URI zmienionego zasobu.
ResourceName Nazwa zmienionego zasobu.
AuditUrl Opcjonalny. URI zapisu audytu.
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"
}

Webhook API

Uwierzytelnianie

Wszystkie wywołania interfejsów API Webhook są uwierzytelniane przy użyciu tokenu Bearer w nagłówku autoryzacji. Uzyskaj token dostępu do https://api.partnercenter.microsoft.com. 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ń obecnie obsługiwanych przez interfejsy API 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: aaaa0000-bb11-2222-33cc-444444dddddd
MS-RequestId: 79419bbb-06ee-48da-8221-e09480537dfc
X-Locale: en-US

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

Rejestrowanie w celu odbierania zdarzeń

Rejestruje klienta do 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: 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" ]
}

Zobacz rejestrację

Zwraca rejestrację zdarzeń Webhooks dla dzierżawcy.

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: cccc2222-dd33-4444-55ee-666666ffffff
MS-RequestId: ca30367d-4b24-4516-af08-74bba6dc6657
X-Locale: en-US

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

Aktualizowanie rejestracji zdarzeń

Aktualizuje istniejącą rejestrację 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: 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" ]
}

Wysyłanie zdarzenia testowego w celu zweryfikowania rejestracji

Generuje zdarzenie testowe w celu zweryfikowania rejestracji Webhooks. Ten test ma na celu sprawdzenie, czy zdarzenia można odbierać z Centrum partnerskiego. Dane dotyczące tych zdarzeń są usuwane 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 obowiązuje limit 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: dddd3333-ee44-5555-66ff-777777aaaaaa
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: eeee4444-ff55-6666-77aa-888888bbbbbb
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US

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

Sprawdź, czy zdarzenie zostało dostarczone

Zwraca bieżący stan zdarzenia weryfikacji. Ta weryfikacja może pomóc w rozwiązywaniu problemów z dostarczaniem wydarzeń. Odpowiedź zawiera wynik dla każdej z prób dostarczenia zdarzenia.

Adres URL zasobu

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

Przykład żądania

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

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

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 ze zdarzeń 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 StreamReaders;
            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}."));
            }
        }
    }
}