Megosztás a következőn keresztül:


Partnerközpont – webhookok

A következőre vonatkozik: Partnerközpont | A 21Vianet által üzemeltetett Partnerközpont | Partnerközpont a Microsoft Cloud for US Government számára

Megfelelő szerepkörök: globális rendszergazda | Számlázási rendszergazda | Rendszergazdai ügynök | Értékesítési ügynök | Segélyszolgálati ügynök

A Partnerközpont Webhook API-kkal a partnerek regisztrálhatnak az erőforrás-változási eseményekre. Ezek az események HTTP POST-k formájában érkeznek a partner regisztrált URL-címére. Ha eseményt szeretne fogadni a Partnerközpontból, a partnerek visszahívást üzemeltetnek, ahol a Partnerközpont közzéteheti az erőforrás-módosítási eseményt. Az esemény digitálisan alá van írva, így a partner ellenőrizheti, hogy a Partnerközpontból küldte-e. A webhookértesítések csak azokra a környezetekre aktiválódnak, amelyek a co-sell legújabb konfigurációját használják.

A Partnerközpont a következő webhookeseményeket támogatja.

  • Észlelt Azure-csalási esemény ("azure-fraud-event-detected")

    Ez az esemény az Azure-ból való csalás észlelésekor jön elő.

  • Delegált rendszergazdai kapcsolat által jóváhagyott esemény ("dap-admin-relationship-approved")

    Ez az esemény akkor jön létre, ha a delegált rendszergazdai jogosultságokat az ügyfél bérlője hagyja jóvá.

  • Az ügyfélesemény által elfogadott viszonteladói kapcsolat ("reseller-relationship-accepted-by-customer")

    Ez az esemény akkor jön elő, amikor az ügyfélbérlemény jóváhagyja a viszonteladói kapcsolatot.

  • Ügyfélesemény által elfogadott közvetett viszonteladói kapcsolat ("indirect-reseller-relationship-accepted-by-customer")

    Ez az esemény akkor jön elő, amikor az ügyfélbérlemény jóváhagyja a közvetett viszonteladói kapcsolatot.

  • Delegált rendszergazdai kapcsolat megszakított eseménye ("dap-admin-relationship-terminated")

    Ez az esemény akkor jön elő, amikor az ügyfél megszünteti a delegált rendszergazdai jogosultságokat.

  • A Microsoft-esemény által megszakított Dap-rendszergazdai kapcsolat ("dap-admin-relationship-terminated-by-microsoft")

    Ez az esemény akkor jön létre, amikor a Microsoft leállítja a DAP-t a partner és az ügyfél bérlője között, ha a DAP több mint 90 napig inaktív.

  • Részletes rendszergazdai hozzáférés-hozzárendelés aktivált eseménye ("granular-admin-access-assignment-activated")

    Ez az esemény akkor jön létre, amikor a partner aktiválja a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését, miután a Microsoft Entra-szerepkörök adott biztonsági csoportokhoz lettek rendelve.

  • Részletes rendszergazdai hozzáférés-hozzárendelés létrehozott eseménye ("granular-admin-access-assignment-created")

    Ez az esemény akkor jön létre, amikor a partner létrehozza a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését. A partnerek adott biztonsági csoportokhoz rendelhetnek ügyfél által jóváhagyott Microsoft Entra-szerepköröket.

  • Részletes rendszergazdai hozzáférés-hozzárendelés törölt eseménye ("granular-admin-access-assignment-deleted")

    Ez az esemény akkor jön elő, amikor a partner törli a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését.

  • Részletes rendszergazdai hozzáférés-hozzárendelés frissített eseménye ("granular-admin-access-assignment-updated")

    Ez az esemény akkor jön elő, amikor a partner frissíti a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését.

  • Részletes rendszergazdai kapcsolat aktivált eseménye ("granular-admin-relationship-activated")

    Ez az esemény akkor jön létre, amikor a részletes delegált rendszergazdai jogosultságok létrejönnek, és aktívak az ügyfél jóváhagyásához.

  • Részletes rendszergazdai kapcsolat által jóváhagyott esemény ("granular-admin-relationship-approved")

    Ez az esemény akkor jön elő, ha az ügyfélbérlemény jóváhagyja a részletes delegált rendszergazdai jogosultságokat.

  • Részletes rendszergazdai kapcsolat lejárt eseménye ("granular-admin-relationship-expired")

    Ez az esemény akkor jön elő, ha a részletes delegált rendszergazdai jogosultságok lejártak.

  • Részletes rendszergazdai kapcsolat által létrehozott esemény ("granular-admin-relationship-created")

    Ez az esemény a részletes delegált rendszergazdai jogosultságok létrehozásakor jön létre.

  • Részletes rendszergazdai kapcsolat frissített eseménye ("részletes-admin-relationship-updated")

    Ez az esemény akkor jelentkezik, ha az ügyfél vagy a partner frissíti a részletes delegált rendszergazdai jogosultságokat.

  • Részletes rendszergazdai kapcsolat automatikus kiterjesztett eseménye ("granular-admin-relationship-auto-extended")

    Ez az esemény akkor jön elő, amikor a rendszer automatikusan kibővíti a részletes delegált rendszergazdai jogosultságokat.

  • Részletes rendszergazdai kapcsolat megszakadt eseménye ("részletes-admin-relationship-terminated")

    Ez az esemény akkor jön elő, ha a partner vagy az ügyfél bérlője leállítja a részletes delegált rendszergazdai jogosultságokat.

  • Számlára kész esemény ("számla-kész")

    Ez az esemény akkor jön létre, amikor az új számla készen áll.

  • Új kereskedelmi migrálás befejezve ("new-commerce-migration-completed")

    Ez az esemény akkor jön létre, amikor az új kereskedelmi migrálás befejeződött.

  • Új kereskedelmi migrálás létrehozva ("new-commerce-migration-created")

    Ez az esemény az új kereskedelmi migrálás létrehozásakor jön létre.

  • Az új kereskedelmi migrálás sikertelen ("new-commerce-migration-failed")

    Ez az esemény akkor jön létre, ha az új kereskedelmi migrálás sikertelen.

  • Átvitel létrehozása ("create-transfer")

    Ez az esemény az átvitel létrehozásakor jön létre.

  • Frissítésátvitel ("update-transfer")

    Ez az esemény az átvitel frissítésekor jelenik meg.

  • Teljes átvitel ("teljes átvitel")

    Ez az esemény az átvitel befejezésekor jön elő.

  • Feladatátvitel ("feladatátvitel")

    Ez az esemény akkor jön elő, ha az átvitel meghiúsul.

  • Az új kereskedelmi áttelepítési ütemezés meghiúsult ("new-commerce-migration-schedule-failed")

    Ez az esemény akkor jön létre, ha az új kereskedelmi migrálási ütemezés meghiúsul.

  • Javaslat által létrehozott esemény ("javaslat létrehozva")

    Ez az esemény az átirányítás létrehozásakor jön létre.

  • Javaslat frissítve esemény ("javaslat frissítve")

    Ez az esemény a javaslat frissítésekor jön létre.

  • Kapcsolódó javaslattal létrehozott esemény ("related-referral-created")

    Ez az esemény a kapcsolódó javaslat létrehozásakor jön létre.

  • Kapcsolódó javaslat frissített eseménye ("related-referral-updated")

    Ez az esemény a kapcsolódó javaslat frissítésekor jön létre.

  • Előfizetés aktív eseménye ("subscription-active")

    Ez az esemény az előfizetés aktiválásakor jön elő.

    Feljegyzés

    Az előfizetés aktív webhookja és a hozzá tartozó tevékenységnapló-esemény jelenleg csak tesztkörnyezet-bérlők számára érhető el.

  • Előfizetés függőben lévő esemény ("előfizetés függőben")

    Ez az esemény akkor jön létre, amikor a megfelelő rendelés sikeresen megérkezett, és az előfizetés létrehozása függőben van.

    Feljegyzés

    Az előfizetés függőben lévő webhook és a kapcsolódó tevékenységnapló-esemény jelenleg csak a tesztkörnyezet-bérlők számára érhető el.

  • Előfizetés megújított eseménye ("előfizetés megújítva")

    Ez az esemény akkor jön elő, amikor az előfizetés befejezi a megújítást.

    Feljegyzés

    Az előfizetés megújított webhookja és a hozzá tartozó tevékenységnapló-esemény jelenleg csak tesztkörnyezet-bérlők számára érhető el.

  • Előfizetés frissítve esemény ("előfizetés frissítve")

    Ez az esemény akkor jön elő, amikor az előfizetés megváltozik. Ezek az események akkor jönnek létre, ha belső változás történik a Partnerközpont API-val végzett módosítások mellett.

    Feljegyzés

    Az előfizetés módosítása és az Előfizetés frissítése esemény aktiválása között akár 48 óra is lehet.

  • Tesztesemény ("teszt-létrehozva")

    Ez az esemény lehetővé teszi a regisztráció önálló előkészítését és tesztelését egy tesztesemény kérésével, majd a folyamat nyomon követésével. Az esemény kézbesítése közben a Microsofttól kapott hibaüzenetek láthatók. Ez a korlátozás csak a "teszt által létrehozott" eseményekre vonatkozik. A hét napnál régebbi adatok törlődnek.

  • Küszöbérték túllépte az eseményt ("usagerecords-thresholdExceeded")

    Ez az esemény akkor keletkezik, ha a Microsoft Azure-használat mennyisége bármely ügyfél esetében meghaladja a használati költségkeretet (a küszöbértéket). További információ: (Azure-költségkeret beállítása az ügyfelek számára/partnerközpont/set-an-azure-spending-budget-for-your-customers).

A jövőbeni webhookesemények olyan erőforrásokhoz lesznek hozzáadva, amelyek a partner által nem irányítható rendszerben változnak, és további frissítéseket végeznek, hogy az események a lehető legközelebb legyenek a "valós idejű" állapothoz. A partnerek visszajelzése arról, hogy mely események adnak értéket a vállalkozásuknak, hasznos annak meghatározásához, hogy milyen új eseményeket kell hozzáadniuk.

A Partnerközpont által támogatott webhookesemények teljes listájáért tekintse meg a Partnerközpont webhookeseményeit.

Előfeltételek

  • Hitelesítő adatok a Partnerközpont hitelesítésében leírtak szerint. Ez a forgatókönyv támogatja az önálló alkalmazással és az App+Felhasználói hitelesítő adatokkal való hitelesítést is.

Események fogadása a Partnerközpontból

Ahhoz, hogy eseményeket fogadjon a Partnerközpontból, közzé kell tennie egy nyilvánosan elérhető végpontot. Mivel ez a végpont elérhető, ellenőriznie kell, hogy a kommunikáció a Partnerközpontból származik-e. Minden fogadott webhookesemény digitálisan alá van írva egy, a Microsoft Roothoz láncolást használó tanúsítvánnyal. Az esemény aláírásához használt tanúsítványra mutató hivatkozást is meg kell adni. Ez lehetővé teszi a tanúsítvány megújítását anélkül, hogy újra kellene üzembe helyeznie vagy újrakonfigurálnia a szolgáltatást. A Partnerközpont 10 kísérletet tesz az esemény kézbesítésére. Ha az esemény még mindig nem érkezik meg 10 kísérlet után, akkor az offline üzenetsorba kerül, és a kézbesítéskor nem történik további kísérlet.

Az alábbi minta egy partnerközpontból közzétett eseményt mutat be.

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

Feljegyzés

Az Engedélyezési fejléc "Signature" (Aláírás) sémával rendelkezik. Ez a tartalom base64 kódolású aláírása.

A visszahívás hitelesítése

A Partnerközponttól kapott visszahívási esemény hitelesítéséhez kövesse az alábbi lépéseket:

  1. Ellenőrizze, hogy vannak-e a szükséges fejlécek (engedélyezés, x-ms-certificate-url, x-ms-signature-algoritmus).
  2. Töltse le a tartalom aláírásához használt tanúsítványt (x-ms-certificate-url).
  3. Ellenőrizze a tanúsítványláncot.
  4. Ellenőrizze a tanúsítvány "szervezetét".
  5. Olvassa el a tartalmat UTF8 kódolással egy pufferbe.
  6. Hozzon létre egy RSA titkosítási szolgáltatót.
  7. Ellenőrizze, hogy az adatok megegyeznek-e a megadott kivonatoló algoritmussal (például SHA256) aláírt adatokkal.
  8. Ha az ellenőrzés sikeres, dolgozza fel az üzenetet.

Feljegyzés

Alapértelmezés szerint az aláírási jogkivonat egy engedélyezési fejlécben lesz elküldve. Ha a SignatureTokenToMsSignatureHeadert igaz értékre állítja be a regisztrációban, az aláírási jogkivonatot a rendszer az x-ms-signature fejlécben küldi el.

Eseménymodell

Az alábbi táblázat egy Partnerközpont-esemény tulajdonságait ismerteti.

Tulajdonságok

Név Leírás
EventName Az esemény neve. A(z) {resource}-{action} formában. Például: "test-created".
ResourceUri A módosított erőforrás URI-ja.
ResourceName A módosított erőforrás neve.
AuditUrl Opcionális. A naplózási rekord URI-ja.
ResourceChangeUtcDate Az erőforrás változásának dátuma és időpontja UTC formátumban.

Minta

Az alábbi minta egy Partnerközpont-esemény struktúráját mutatja be.

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

Hitelesítés

A Webhook API-khoz intézett összes hívás hitelesítése az Engedélyezési fejléc Tulajdonos jogkivonatával történik. Hozzáférési jogkivonat beszerzése a hozzáféréshez https://api.partnercenter.microsoft.com. Ez a jogkivonat ugyanaz a jogkivonat, amely a Partnerközpont többi API-jának eléréséhez használatos.

Események listájának lekérése

A Webhook API-k által jelenleg támogatott események listáját adja vissza.

Erőforrás URL-címe

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

Példa kérése

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

Példa válaszra

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

Regisztráció események fogadásához

Regisztrál egy bérlőt a megadott események fogadásához.

Erőforrás URL-címe

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

Példa kérése

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

Példa válaszra

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

Regisztráció megtekintése

Egy bérlő Webhooks-eseményregisztrációját adja vissza.

Erőforrás URL-címe

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

Példa kérése

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

Példa válaszra

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

Eseményregisztráció frissítése

Frissíti a meglévő eseményregisztrációt.

Erőforrás URL-címe

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

Példa kérése

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

Példa válaszra

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

Tesztesemény küldése a regisztráció ellenőrzéséhez

Létrehoz egy teszteseményt a Webhooks-regisztráció ellenőrzéséhez. Ez a teszt annak ellenőrzésére szolgál, hogy fogadhat-e eseményeket a Partnerközpontból. Az események adatai hét nappal a kezdeti esemény létrehozása után törlődnek. Az érvényesítési esemény elküldése előtt regisztrálnia kell magát a "teszt által létrehozott" eseményre a regisztrációs API használatával.

Feljegyzés

Az érvényesítési esemény közzétételekor percenként 2 kérelemre vonatkozó korlátozás van érvényben.

Erőforrás URL-címe

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

Példa kérése

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:

Példa válaszra

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

Az esemény kézbesítésének ellenőrzése

Az érvényesítési esemény aktuális állapotát adja vissza. Ez az ellenőrzés hasznos lehet az eseménykézbesítési problémák elhárításához. A válasz minden egyes, az esemény kézbesítésére tett kísérlet eredményét tartalmazza.

Erőforrás URL-címe

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

Példa kérése

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

Példa válaszra

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

Példa aláírás-ellenőrzésre

Minta visszahívásvezérlő aláírása (ASP.NET)

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

Aláírás érvényesítése

Az alábbi példa bemutatja, hogyan adhat hozzá engedélyezési attribútumot ahhoz a vezérlőhöz, amely visszahívásokat fogad a Webhook-eseményekről.

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