Webhook Pusat Mitra

Berlaku untuk: Pusat Mitra | Pusat Mitra yang dioperasikan oleh 21Vianet | Pusat Mitra untuk Microsoft Cloud untuk Pemerintah AS

Peran yang sesuai: Admin global | Admin Penagihan | Agen admin | Agen penjualan | Agen helpdesk

API Webhook Pusat Mitra memungkinkan mitra untuk mendaftar untuk peristiwa perubahan sumber daya. Peristiwa ini dikirimkan dalam bentuk HTTP POST ke URL terdaftar mitra. Untuk menerima acara dari Pusat Mitra, mitra akan menghosting panggilan balik di mana Pusat Mitra dapat MEMPOSTING peristiwa perubahan sumber daya. Acara akan ditandatangani secara digital sehingga mitra dapat memverifikasi bahwa kejadian tersebut dikirim dari Pusat Mitra. Pemberitahuan webhook hanya dipicu ke lingkungan yang memiliki konfigurasi terbaru untuk Penjualan bersama.

Mitra dapat memilih dari peristiwa Webhook, seperti contoh berikut, yang didukung oleh Pusat Mitra.

  • Peristiwa Penipuan Azure Terdeteksi ("terdeteksi peristiwa penipuan azure")

    Kejadian ini dimunculkan saat peristiwa penipuan Azure terdeteksi.

  • Peristiwa yang Disetujui Hubungan Admin yang Didelegasikan ("dap-admin-relationship-approved")

    Kejadian ini dimunculkan ketika Hak Istimewa Admin yang Didelegasikan disetujui oleh penyewa pelanggan.

  • Hubungan Penjual Diterima oleh Peristiwa Pelanggan ("reseller-relationship-accepted-by-customer")

    Kejadian ini dimunculkan ketika Hubungan Penjual disetujui oleh penyewa pelanggan.

  • Peristiwa Penghentian Hubungan Admin yang Didelegasikan ("dap-admin-relationship-terminated")

    Kejadian ini dimunculkan ketika Hak Istimewa Admin yang Didelegasikan dihentikan oleh pelanggan.

  • Hubungan Admin Dap Dihentikan Oleh Peristiwa Microsoft ("dap-admin-relationship-terminated-by-microsoft")

    Kejadian ini dimunculkan ketika Microsoft mengakhiri DAP antara penyewa Mitra dan Pelanggan saat DAP tidak aktif selama lebih dari 90 hari.

  • Peristiwa Yang Diaktifkan Penetapan Akses Admin Granular ("granular-admin-access-assignment-activated")

    Kejadian ini dimunculkan ketika penetapan akses Hak Istimewa Admin Yang Didelegasikan Granular diaktifkan oleh mitra setelah peran Microsoft Entra ditetapkan ke grup keamanan tertentu.

  • Peristiwa Yang Dibuat Penetapan Akses Admin Granular ("granular-admin-access-assignment-created")

    Kejadian ini dimunculkan ketika penetapan akses Hak Istimewa Admin Yang Didelegasikan Granular dibuat oleh mitra. Mitra dapat menetapkan peran Microsoft Entra yang disetujui pelanggan ke grup keamanan tertentu.

  • Peristiwa Penghapusan Penetapan Akses Admin Granular ("granular-admin-access-assignment-deleted")

    Kejadian ini dimunculkan ketika penetapan akses Hak Istimewa Admin Yang Didelegasikan Granular dihapus oleh mitra.

  • Peristiwa Terbaru Penetapan Akses Admin Granular ("granular-admin-access-assignment-updated")

    Kejadian ini dimunculkan ketika penetapan akses Hak Istimewa Admin Yang Didelegasikan Granular diperbarui oleh mitra.

  • Peristiwa Yang Diaktifkan Hubungan Admin Granular ("granular-admin-relationship-activated")

    Kejadian ini dimunculkan ketika Hak Istimewa Admin Yang Didelegasikan Granular dibuat dan aktif untuk disetujui pelanggan.

  • Peristiwa Yang Disetujui Hubungan Admin Granular ("granular-admin-relationship-approved")

    Kejadian ini dinaikkan ketika Hak Istimewa Admin Yang Didelegasikan Granular disetujui oleh penyewa pelanggan.

  • Peristiwa Kedaluwarsa Hubungan Admin Granular ("granular-admin-relationship-expired")

    Kejadian ini dinaikkan ketika Hak Istimewa Admin Yang Didelegasikan Granular kedaluwarsa.

  • Peristiwa Pembaruan Hubungan Admin Granular ("granular-admin-relationship-updated")

    Kejadian ini dimunculkan ketika Hak Istimewa Admin Yang Didelegasikan Granular diperbarui oleh penyewa mitra/pelanggan.

  • Peristiwa Perluasan Otomatis Hubungan Admin Granular ("granular-admin-relationship-auto-extended")

    Kejadian ini dinaikkan ketika Hak Istimewa Admin Yang Didelegasikan Granular diperluas secara otomatis oleh sistem.

  • Peristiwa Dihentikan Hubungan Admin Granular ("granular-admin-relationship-terminated")

    Kejadian ini dimunculkan ketika Hak Istimewa Admin Yang Didelegasikan Granular dihentikan oleh penyewa mitra/pelanggan.

  • Migrasi Perdagangan Baru Selesai ("new-commerce-migration-completed")

    Kejadian ini dimunculkan ketika migrasi perdagangan baru selesai.

  • Migrasi Perdagangan Baru Dibuat ("new-commerce-migration-create")

    Kejadian ini dimunculkan saat migrasi perdagangan baru dibuat.

  • Migrasi Perdagangan Baru Gagal ("new-commerce-migration-failed")

    Kejadian ini dimunculkan ketika migrasi perdagangan baru gagal.

  • Jadwal Migrasi Perdagangan Baru Gagal ("new-commerce-migration-schedule-schedule-failed")

    Kejadian ini dimunculkan ketika jadwal migrasi perdagangan baru gagal.

  • Peristiwa yang Dibuat Rujukan ("dibuat rujukan")

    Kejadian ini dimunculkan ketika rujukan dibuat.

  • Peristiwa Yang Diperbarui Rujukan ("diperbarui rujukan")

    Kejadian ini dimunculkan ketika rujukan diperbarui.

  • Peristiwa Yang Dibuat Rujukan Terkait ("terkait-rujukan-dibuat")

    Kejadian ini dimunculkan ketika rujukan terkait dibuat.

  • Peristiwa Yang Diperbarui Rujukan Terkait ("terkait-rujukan-diperbarui")

    Kejadian ini dimunculkan ketika rujukan terkait diperbarui.

  • Peristiwa Yang Diperbarui Langganan ("langganan-diperbarui")

    Kejadian ini dimunculkan ketika langganan berubah. Peristiwa ini akan dihasilkan ketika ada perubahan internal selain ketika perubahan dilakukan melalui API Pusat Mitra.

    Catatan

    Ada penundaan hingga 48 jam antara waktu langganan berubah dan saat peristiwa Diperbarui Langganan dipicu.

  • Peristiwa Pengujian ("dibuat pengujian")

    Kejadian ini memungkinkan Anda untuk melakukan onboard sendiri dan menguji pendaftaran Anda dengan meminta peristiwa pengujian lalu melacak kemajuannya. Anda dapat melihat pesan kegagalan yang diterima dari Microsoft saat mencoba mengirimkan peristiwa. Pembatasan ini hanya berlaku untuk peristiwa "yang dibuat pengujian". Data yang lebih lama dari tujuh hari akan dibersihkan.

  • Peristiwa Terlampaui Ambang Batas ("usagerecords-thresholdExceeded")

    Kejadian ini dinaikkan ketika jumlah penggunaan Microsoft Azure untuk setiap pelanggan melebihi anggaran pengeluaran penggunaan mereka (ambang batasnya). Untuk informasi selengkapnya, lihat (Mengatur anggaran pengeluaran Azure untuk pelanggan/pusat mitra/set-an-azure-spending-budget-for-your-customers).

Peristiwa Webhook di masa mendatang akan ditambahkan untuk sumber daya yang berubah dalam sistem yang tidak dikendalikan mitra, dan pembaruan lebih lanjut akan dilakukan untuk mendapatkan peristiwa tersebut sedekat mungkin dengan "real time". Umpan balik dari Mitra tentang peristiwa mana yang menambah nilai ke bisnis mereka akan berguna dalam menentukan peristiwa baru apa yang akan ditambahkan.

Untuk daftar lengkap peristiwa Webhook yang didukung oleh Pusat Mitra, lihat Peristiwa webhook Pusat Mitra.

Prasyarat

  • Kredensial seperti yang dijelaskan dalam autentikasi Pusat Mitra. Skenario ini mendukung autentikasi dengan kredensial Aplikasi mandiri dan Aplikasi+Pengguna.

Menerima peristiwa dari Pusat Mitra

Untuk menerima peristiwa dari Pusat Mitra, Anda harus mengekspos titik akhir yang dapat diakses publik. Karena titik akhir ini terekspos, Anda harus memvalidasi bahwa komunikasi berasal dari Pusat Mitra. Semua peristiwa Webhook yang Anda terima ditandatangani secara digital dengan sertifikat yang ditautkan ke Microsoft Root. Tautan ke sertifikat yang digunakan untuk menandatangani peristiwa juga akan disediakan. Ini akan memungkinkan sertifikat diperbarui tanpa Anda harus menyebarkan ulang atau mengonfigurasi ulang layanan Anda. Pusat Mitra akan melakukan 10 upaya untuk mengirimkan acara. Jika peristiwa masih belum dikirimkan setelah 10 upaya, peristiwa tersebut akan dipindahkan ke antrean offline dan tidak ada upaya lebih lanjut yang akan dilakukan saat pengiriman.

Contoh berikut menunjukkan peristiwa yang diposting dari Pusat Mitra.

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

Catatan

Header Otorisasi memiliki skema "Tanda Tangan". Ini adalah tanda tangan konten yang dikodekan base64.

Cara mengautentikasi panggilan balik

Untuk mengautentikasi peristiwa panggilan balik yang diterima dari Pusat Mitra, ikuti langkah-langkah berikut:

  1. Verifikasi header yang diperlukan ada (Otorisasi, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Unduh sertifikat yang digunakan untuk menandatangani konten (x-ms-certificate-url).

  3. Verifikasi Rantai Sertifikat.

  4. Verifikasi "Organisasi" sertifikat.

  5. Baca konten dengan pengodean UTF8 ke dalam buffer.

  6. Buat Penyedia Kripto RSA.

  7. Verifikasi data cocok dengan apa yang ditandatangani dengan algoritma hash yang ditentukan (misalnya SHA256).

  8. Jika verifikasi berhasil, proses pesan.

Catatan

Secara default, token tanda tangan akan dikirim di header Otorisasi. Jika Anda mengatur SignatureTokenToMsSignatureHeader ke true dalam pendaftaran Anda, token tanda tangan akan dikirim di header x-ms-signature sebagai gantinya.

Model peristiwa

Tabel berikut ini menjelaskan properti peristiwa Pusat Mitra.

Properti

Nama Deskripsi
EventName Nama peristiwa. Dalam formulir {resource}-{action}. Misalnya, "test-create".
ResourceUri URI sumber daya yang berubah.
ResourceName Nama sumber daya yang berubah.
AuditUrl Opsional. URI catatan Audit.
ResourceChangeUtcDate Tanggal dan waktu, dalam format UTC, ketika perubahan sumber daya terjadi.

Sampel

Sampel berikut menunjukkan struktur peristiwa Pusat Mitra.

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

API Webhook

Autentikasi

Semua panggilan ke API Webhook diautentikasi menggunakan token Pembawa di Header Otorisasi. Dapatkan token akses untuk mengakses https://api.partnercenter.microsoft.com. Token ini adalah token yang sama yang digunakan untuk mengakses API Pusat Mitra lainnya.

Mendapatkan daftar peristiwa

Mengembalikan daftar peristiwa yang saat ini didukung oleh API Webhook.

URL Sumber Daya

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

Contoh permintaan

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

Contoh tanggapan

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

Daftar untuk menerima peristiwa

Mendaftarkan penyewa untuk menerima peristiwa yang ditentukan.

URL Sumber Daya

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

Contoh permintaan

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

Contoh tanggapan

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

Melihat pendaftaran

Mengembalikan pendaftaran peristiwa Webhooks untuk penyewa.

URL Sumber Daya

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

Contoh permintaan

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

Contoh tanggapan

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

Memperbarui pendaftaran peristiwa

Memperbarui pendaftaran peristiwa yang ada.

URL Sumber Daya

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

Contoh permintaan

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

Contoh tanggapan

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

Mengirim peristiwa pengujian untuk memvalidasi pendaftaran Anda

Menghasilkan peristiwa pengujian untuk memvalidasi pendaftaran Webhook. Pengujian ini dimaksudkan untuk memvalidasi bahwa Anda dapat menerima peristiwa dari Pusat Mitra. Data untuk peristiwa ini akan dihapus tujuh hari setelah peristiwa awal dibuat. Anda harus terdaftar untuk peristiwa "yang dibuat pengujian", menggunakan API pendaftaran, sebelum mengirim peristiwa validasi.

Catatan

Ada batas pembatasan 2 permintaan per menit saat memposting peristiwa validasi.

URL Sumber Daya

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

Contoh permintaan

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:

Contoh tanggapan

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

Verifikasi bahwa peristiwa telah dikirimkan

Mengembalikan status kejadian validasi saat ini. Verifikasi ini dapat membantu untuk memecahkan masalah pengiriman peristiwa. Respons berisi hasil untuk setiap upaya yang dilakukan untuk mengirimkan peristiwa.

URL Sumber Daya

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

Contoh permintaan

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

Contoh tanggapan

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

Contoh untuk Validasi Tanda Tangan

Contoh tanda tangan Pengontrol Panggilan Balik (ASP.NET)

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

Validasi Tanda Tangan

Contoh berikut menunjukkan cara menambahkan Atribut Otorisasi ke pengontrol yang menerima panggilan balik dari peristiwa 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}."));
            }
        }
    }
}