Udostępnij przez


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

Zamiast stale sprawdzać interfejsy API Centrum partnerskiego pod kątem aktualizacji, partnerzy mogą konfigurować elementy webhook, aby otrzymywać powiadomienia błyskawiczne w przypadku zmian — takich jak subskrypcja klienta lub oferta platformy handlowej. Za każdym razem, gdy wystąpi określone zdarzenie, system wysyła wiadomość HTTP POST do zarejestrowanego adresu URL partnera, aby od razu mógł odpowiedzieć. Na przykład partnerzy mogą szybko obsługiwać zmiany subskrypcji lub śledzić migracje w miarę ich wystąpienia.

Najważniejsze zalety:

  • Zautomatyzowane przepływy pracy: Elementy webhook dbają o rutynowe kontrole, więc partnerzy nie muszą ręcznie szukać aktualizacji. Usprawnia to procesy biznesowe i oszczędza czas.
  • Alerty natychmiastowe: Partnerzy otrzymują powiadomienia, gdy tylko wystąpią ważne zdarzenia, takie jak zmiana subskrypcji klienta, zawieszenie lub anulowanie. Oznacza to, że mogą działać szybciej i zapewnić lepszą obsługę.
  • Zarządzanie ofertami SaaS: Wydawcy mogą używać webhooków, aby zachować stan subskrypcji SaaS w synchronizacji z Centrum partnerskim, zapewniając aktualizację po obu stronach transakcji.
  • Śledzenie migracji: Elementy webhook ułatwiają partnerom automatyzowanie i monitorowanie migracji subskrypcji klientów do nowej platformy handlowej. Większa wydajność: polegając na powiadomieniach o zdarzeniach zamiast zapytań o aktualizacje, partnerzy zmniejszają obciążenie swoich systemów i interfejsów API Centrum Partnerów.

Jak działają webhooks w Centrum Partnerów

Webhook APIs 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.

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

    To zdarzenie jest zgłaszane, gdy delegowane uprawnienia administratora są zatwierdzane przez dzierżawcę klienta.

  • Relacja z odsprzedawcą zaakceptowana przez klienta ("reseller-relationship-accepted-by-customer")

    To zdarzenie jest zgłaszane, gdy klient-dzierżawca zatwierdza współpracę z odsprzedawcą.

  • Zdarzenie zaakceptowania relacji z odsprzedawcą pośrednim przez klienta ("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.

  • Zakończenie relacji DAP przez Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    To zdarzenie jest inicjowane, gdy firma Microsoft rozwiązuje DAP między dzierżawą Partnera a Klienta, gdy usługa DAP jest nieaktywna przez ponad 90 dni.

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

    Zdarzenie to jest zgłaszane, gdy partner aktywuje dostęp w ramach szczegółowych uprawnień administratora delegowanego, po przypisaniu ról Microsoft Entra do określonych grup zabezpieczeń.

  • Zdarzenie utworzenia szczegółowego przypisania dostępu administratora ("granular-admin-access-assignment-created")

    To zdarzenie jest zgłaszane, gdy partner dokonuje przypisania dostępu do granularnych 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 szczegółowych delegowanych uprawnień administratora.

  • Zaktualizowane zdarzenie przypisania granularnego dostępu administratora ("granular-admin-access-assignment-updated")

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

  • Zdarzenie aktywacji relacji granularnego administratora ("aktywacja relacji granularnego administratora")

    To zdarzenie jest zgłaszane, gdy granularne delegowane uprawnienia administratora zostaną utworzone i będą aktywne, w oczekiwaniu na zatwierdzenie przez klienta.

  • Granularne wydarzenie zatwierdzenia relacji administratora ("granular-admin-relationship-approved")

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

  • Zdarzenie wygaśnięcia szczegółowej relacji administratora ("wygaśnięcie-szczegółowej-relacji-administratora")

    To zdarzenie jest generowane, gdy granularne delegowane uprawnienia administracyjne wygasną.

  • Zdarzenie utworzenia drobiazgowej relacji administratora ("utworzenie-drobiazgowej-relacji-administratora")

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

  • Zaktualizowane zdarzenie granularnej relacji administratora ("granularna relacja administratora zaktualizowana")

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

  • Zdarzenie automatycznego rozszerzenia relacji administracyjnej o szczegółowy charakter ("granularne rozszerzenie auto")

    To zdarzenie jest wyzwalane, gdy system automatycznie rozszerza Granular Delegated Admin Privileges.

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

    To zdarzenie jest zgłaszane, gdy partner lub klient anuluje szczegółowe uprawnienia delegowanego administratora.

  • Zakończono migrację w ramach 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 nowego handlu 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 generowane podczas tworzenia transferu.

  • Aktualizacja transferu ("update-transfer")

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

  • Pełne 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 polecenia ("utworzenie-polecenia")

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

  • Zdarzenie aktualizacji rekomendacji ("aktualizacja-rekomendacji")

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

  • Powiązane zdarzenie utworzenia odwołania ("related-referral-created")

    To zdarzenie jest generowane podczas tworzenia powiązanego skierowania.

  • Powiązana aktualizacja referencji ("related-referral-updated")

    To zdarzenie jest wyzwalane po zaktualizowaniu powiązanego skierowania.

  • Zdarzenie aktywacji subskrypcji ("aktywna subskrypcja")

    To zdarzenie jest zgłaszane po aktywowaniu subskrypcji.

    Uwaga

    Webhook aktywnej subskrypcji i odpowiadające mu zdarzenie w Dzienniku Aktywności są obecnie dostępne tylko dla dzierżawców środowiska testowego.

  • Zdarzenie oczekiwania na realizację subskrypcji ("oczekiwanie na realizację subskrypcji")

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

    Uwaga

    Oczekujący element webhook subskrypcji i odpowiadające mu zdarzenie dziennika aktywności są obecnie dostępne tylko dla dzierżaw piaskownicy.

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

  • Zaktualizowanie zdarzenia 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

    Jest opóźnienie wynoszące do 48 godzin między momentem zmiany subskrypcji a wyzwoleniem zdarzenia Aktualizacja subskrypcji.

  • Zdarzenie testowe ("test-utworzone")

    To wydarzenie umożliwia samodzielne rozpoczęcie procesu i przetestowanie 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).

Przyszłe zdarzenia webhook zostaną dodane dla zasobów, które zmieniają się w systemie, nad którym partner nie ma kontroli, a dalsze aktualizacje zostaną wprowadzone, aby te zdarzenia jak najbardziej odpowiadały rzeczywistemu czasowi. 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, jak opisano w Uwierzytelnianiu w Centrum Partnerów. Ten scenariusz obsługuje uwierzytelnianie zarówno przy użyciu samodzielnych poświadczeń aplikacji, jak i poświadczeń aplikacji+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. Zostanie również 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 dostarczenia wydarzenia. 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 Autoryzacji ma typ "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, przeprocesuj komunikat.

Uwaga

Domyślnie token podpisu jest wysyłany w nagłówku autoryzacji. Jeśli ustawisz parametr 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 Partner Center.

Właściwości

Nazwa/nazwisko opis
EventName Nazwa zdarzenia. W formacie {resource}-{action}. Na przykład "test-created".
ResourceUri URI zasobu, który został zmieniony.
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 najemcę 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 webhooków. 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 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/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 powiadomienia 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}."));
            }
        }
    }
}