Webhooks de l’Espace partenaires

S’applique à : Espace partenaires | Espace partenaires géré par 21Vianet | Espace partenaires de Microsoft Cloud for US Government

Rôles appropriés : Administrateur général | Administrateur de facturation | Agent d’administration | Agent commercial | Agent du support technique

Les API webhook de l’Espace partenaires permettent aux partenaires de s’inscrire aux événements de modification des ressources. Ces événements sont transmis sous forme de HTTP POST à l’URL enregistrée par le partenaire. Pour recevoir un événement de l’Espace partenaires, les partenaires hébergent un rappel dans lequel l’Espace partenaires peut publier l’événement de modification des ressources. L’événement est signé numériquement afin que le partenaire puisse vérifier qu’il a été envoyé à partir de l’Espace partenaires. Les notifications webhook sont déclenchées uniquement dans l’environnement qui a la dernière configuration pour la co-vente.

L’Espace partenaires prend en charge les événements Webhook suivants.

  • Événement de fraude Azure détecté (« azure-fraud-event-détecté »)

    Cet événement est déclenché lorsque l’événement de fraude Azure est détecté.

  • Événement approuvé de relation d’administration déléguée (« dap-admin-relationship-approved »)

    Cet événement est déclenché lorsque les privilèges d’administrateur délégués sont approuvés par le locataire client.

  • Relation de revendeur acceptée par l’événement client (« reseller-relationship-accepted-by-customer »)

    Cet événement est déclenché lorsque le client client approuve la relation de revendeur.

  • Relation de revendeur indirect acceptée par l’événement client (« indirect-reseller-relationship-accepted-by-customer »)

    Cet événement est déclenché lorsque le client client approuve la relation de revendeur indirect.

  • Événement d’arrêt de la relation d’administration déléguée (« dap-admin-relationship-terminated »)

    Cet événement est déclenché lorsque le client met fin aux privilèges d’administrateur délégués.

  • Relation d’administration Dap terminée par l’événement Microsoft (« dap-admin-relationship-terminated-by-microsoft »)

    Cet événement est déclenché lorsque Microsoft met fin à DAP entre le partenaire et le client lorsque DAP est inactif pendant plus de 90 jours.

  • Événement d’affectation d’accès administrateur granulaire activé (« granular-admin-access-assignment-activated »)

    Cet événement est déclenché lorsque le partenaire active l’attribution granulaire des privilèges d’administrateur délégués une fois que les rôles Microsoft Entra sont attribués à des groupes de sécurité spécifiques.

  • Événement granulaire d’attribution d’accès administrateur créé (« granular-admin-access-assignment-created »)

    Cet événement est déclenché lorsque le partenaire crée l’attribution d’accès aux privilèges d’administrateur délégué granulaire. Les partenaires peuvent attribuer des rôles Microsoft Entra approuvés par le client à des groupes de sécurité spécifiques.

  • Événement granulaire d’attribution d’accès administrateur supprimé (« granular-admin-access-assignment-deleted »)

    Cet événement est déclenché lorsque le partenaire supprime l’attribution d’accès aux privilèges d’administrateur délégué granulaire.

  • Événement de mise à jour de l’attribution d’accès administrateur granulaire (« granular-admin-access-assignment-updated »)

    Cet événement est déclenché lorsque le partenaire met à jour l’attribution d’accès aux privilèges d’administrateur délégué granulaires.

  • Événement de relation d’administration granulaire activé (« granular-admin-relationship-activated »)

    Cet événement est déclenché lorsque les privilèges d’administrateur délégué granulaires sont créés et actifs pour que le client approuve.

  • Événement approuvé de relation d’administration granulaire (« granular-admin-relationship-approved »)

    Cet événement est déclenché lorsque le client client approuve les privilèges d’administrateur délégué granulaires.

  • Événement de relation d’administration granulaire expiré (« granular-admin-relationship-expire »)

    Cet événement est déclenché lorsque les privilèges d’administrateur délégué granulaires ont expiré.

  • Événement Gran Admin Relationship Created (« granular-admin-relationship-created »)

    Cet événement est déclenché lorsque les privilèges d’administrateur délégué granulaires sont créés.

  • Événement de mise à jour de la relation d’administration granulaire (« granular-admin-relationship-updated »)

    Cet événement est déclenché lorsque le client ou le partenaire met à jour les privilèges d’administrateur délégué granulaires.

  • Événement étendu automatique de relation d’administration granulaire (« granular-admin-relationship-auto-extended »)

    Cet événement est déclenché lorsque le système étend automatiquement les privilèges d’administrateur délégué granulaires.

  • Événement granulaire de relation d’administration terminée (« granular-admin-relationship-terminated »)

    Cet événement est déclenché lorsque le partenaire ou le locataire client met fin aux privilèges d’administrateur délégué granulaires.

  • Événement Invoice Ready (« invoice-ready »)

    Cet événement est déclenché lorsque la nouvelle facture est prête.

  • Nouvelle migration commerciale terminée (« new-commerce-migration-completed »)

    Cet événement est déclenché lorsque la nouvelle migration commerciale est terminée.

  • Nouvelle migration de commerce créée (« new-commerce-migration-created »)

    Cet événement est déclenché lorsque la nouvelle migration commerciale est créée.

  • Échec de la migration du nouveau commerce (« new-commerce-migration-failed »)

    Cet événement est déclenché lorsque la nouvelle migration commerciale a échoué.

  • Créer un transfert (« create-transfer »)

    Cet événement est déclenché lors de la création du transfert.

  • Mettre à jour le transfert (« update-transfer »)

    Cet événement est déclenché lorsque le transfert est mis à jour.

  • Transfert complet (« transfert complet »)

    Cet événement est déclenché lorsque le transfert est terminé.

  • Échec du transfert (« échec-transfert »)

    Cet événement est déclenché lorsque le transfert échoue.

  • Échec de la planification de la migration du nouveau commerce (« new-commerce-migration-schedule-failed »)

    Cet événement est déclenché lorsque la nouvelle planification de migration commerciale a échoué.

  • Événement de création de référence (« référence créée »)

    Cet événement est déclenché lors de la création de la référence.

  • Événement de mise à jour de référence (« référence mise à jour »)

    Cet événement est déclenché lorsque la référence est mise à jour.

  • Événement de création de référence connexe (« related-referral-created »)

    Cet événement est déclenché lorsque la référence associée est créée.

  • Événement de mise à jour de référence connexe (« related-referral-updated »)

    Cet événement est déclenché lorsque la référence associée est mise à jour.

  • Événement d’abonnement actif (« abonnement actif »)

    Cet événement est déclenché lorsque l’abonnement est activé.

    Remarque

    Le webhook Actif de l’abonnement et l’événement de journal d’activité correspondants sont uniquement disponibles pour les locataires de bac à sable pour l’instant.

  • Événement en attente d’abonnement (« abonnement en attente »)

    Cet événement est déclenché lorsque la commande correspondante a été correctement reçue et que la création de l’abonnement est en attente.

    Remarque

    Le webhook en attente d’abonnement et l’événement de journal d’activité correspondants sont uniquement disponibles pour les locataires de bac à sable pour l’instant.

  • Événement renouvelé d’abonnement (« abonnement renouvelé »)

    Cet événement est déclenché lorsque l’abonnement termine le renouvellement.

    Remarque

    Le webhook renouvelé de l’abonnement et l’événement de journal d’activité correspondants sont uniquement disponibles pour les locataires de bac à sable pour l’instant.

  • Événement mis à jour de l’abonnement (« abonnement mis à jour »)

    Cet événement est déclenché lorsque l’abonnement change. Ces événements sont générés lorsqu’il existe une modification interne en plus du moment où des modifications sont apportées via l’API espace partenaires.

    Remarque

    Il existe un délai allant jusqu’à 48 heures entre le moment où un abonnement change et lorsque l’événement Subscription Updated est déclenché.

  • Événement de test (« test créé »)

    Cet événement vous permet d’intégrer et de tester automatiquement votre inscription en demandant un événement de test, puis en suivant sa progression. Vous pouvez voir les messages d’échec reçus par Microsoft lors de la tentative de remise de l’événement. Cette restriction s’applique uniquement aux événements « créés par test ». Les données antérieures à sept jours sont vidées.

  • Threshold Exceeded Event (« usagerecords-thresholdExceeded »)

    Cet événement est déclenché lorsque la quantité d’utilisation de Microsoft Azure pour tous les clients dépasse leur budget de dépense d’utilisation (leur seuil). Pour plus d’informations, consultez (Définir un budget de dépense Azure pour vos clients/partner-center/set-an-azure-spending-budget-for-your-customers).

Les événements webhook futurs seront ajoutés pour les ressources qui changent dans le système dont le partenaire n’est pas le contrôle, et d’autres mises à jour seront apportées pour obtenir ces événements aussi près de « temps réel » que possible. Les commentaires des partenaires sur lesquels les événements ajoutent de la valeur à leur entreprise sont utiles pour déterminer les nouveaux événements à ajouter.

Pour obtenir la liste complète des événements webhook pris en charge par l’Espace partenaires, consultez les événements webhook de l’Espace partenaires.

Prérequis

  • Informations d’identification, comme décrit dans Authentification auprès de l’Espace partenaires. Ce scénario prend en charge l’authentification avec les informations d’identification d’application et d’application+utilisateur autonomes.

Réception d’événements à partir de l’Espace partenaires

Pour recevoir des événements à partir de l’Espace partenaires, vous devez exposer un point de terminaison accessible publiquement. Étant donné que ce point de terminaison est exposé, vous devez vérifier que la communication provient de l’Espace partenaires. Tous les événements Webhook que vous recevez sont signés numériquement avec un certificat qui se chaîne à la racine Microsoft. Un lien vers le certificat utilisé pour signer l’événement est également fourni. Cela permet au certificat d’être renouvelé sans avoir à redéployer ou reconfigurer votre service. L’Espace partenaires effectue 10 tentatives de remise de l’événement. Si l’événement n’est toujours pas remis après 10 tentatives, il est déplacé dans une file d’attente hors connexion et aucune autre tentative n’est effectuée à la remise.

L’exemple suivant montre un événement publié à partir de l’Espace partenaires.

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

Remarque

L’en-tête d’autorisation a un schéma de « Signature ». Il s’agit d’une signature encodée en base64 du contenu.

Comment authentifier le rappel

Pour authentifier l’événement de rappel reçu à partir de l’Espace partenaires, procédez comme suit :

  1. Vérifiez que les en-têtes requis sont présents (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).
  2. Téléchargez le certificat utilisé pour signer le contenu (x-ms-certificate-url).
  3. Vérifiez la chaîne de certificats.
  4. Vérifiez l'« organisation » du certificat.
  5. Lisez le contenu avec l’encodage UTF8 dans une mémoire tampon.
  6. Créez un fournisseur de chiffrement RSA.
  7. Vérifiez que les données correspondent à ce qui a été signé avec l’algorithme de hachage spécifié (par exemple SHA256).
  8. Si la vérification réussit, traitez le message.

Remarque

Par défaut, le jeton de signature est envoyé dans un en-tête d’autorisation. Si vous définissez SignatureTokenToMsSignatureHeader sur true dans votre inscription, le jeton de signature est envoyé dans l’en-tête x-ms-signature à la place.

Modèle d’événement

Le tableau suivant décrit les propriétés d’un événement espace partenaires.

Propriétés

Nom Description
EventName Nom de l’événement. Sous la forme {resource}-{action}. Par exemple, « test-created ».
ResourceUri URI de la ressource qui a changé.
ResourceName Nom de la ressource qui a changé.
AuditUrl facultatif. URI de l’enregistrement Audit.
ResourceChangeUtcDate Date et heure, au format UTC, lorsque la modification de la ressource s’est produite.

Exemple

L’exemple suivant montre la structure d’un événement espace partenaires.

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

Authentification

Tous les appels aux API Webhook sont authentifiés à l’aide du jeton du porteur dans l’en-tête d’autorisation. Acquérir un jeton d’accès pour accéder https://api.partnercenter.microsoft.com. Ce jeton est le même que celui utilisé pour accéder au reste des API de l’Espace partenaires.

Obtenir la liste des événements

Retourne une liste des événements actuellement pris en charge par les API Webhook.

URL de la ressource

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

Exemple de requête

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

Exemple de réponse

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

S’inscrire pour recevoir des événements

Inscrit un locataire pour recevoir les événements spécifiés.

URL de la ressource

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

Exemple de requête

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

Exemple de réponse

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

Afficher une inscription

Retourne l’inscription d’événements Webhooks pour un locataire.

URL de la ressource

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

Exemple de requête

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

Exemple de réponse

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

Mettre à jour une inscription d’événement

Met à jour une inscription d’événement existante.

URL de la ressource

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

Exemple de requête

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

Exemple de réponse

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

Envoyer un événement de test pour valider votre inscription

Génère un événement de test pour valider l’inscription des Webhooks. Ce test est destiné à valider que vous pouvez recevoir des événements de l’Espace partenaires. Les données de ces événements sont supprimées sept jours après la création de l’événement initial. Vous devez être inscrit pour l’événement « test-created », à l’aide de l’API d’inscription, avant d’envoyer un événement de validation.

Remarque

Il existe une limite de 2 requêtes par minute lors de la publication d’un événement de validation.

URL de la ressource

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

Exemple de requête

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:

Exemple de réponse

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

Vérifier que l’événement a été remis

Retourne l’état actuel de l’événement de validation. Cette vérification peut être utile pour résoudre les problèmes de remise d’événements. La réponse contient un résultat pour chaque tentative effectuée pour remettre l’événement.

URL de la ressource

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

Exemple de requête

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

Exemple de réponse

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

Exemple de validation de signature

Exemple de signature du contrôleur de rappel (ASP.NET)

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

Signature Validation

L’exemple suivant montre comment ajouter un attribut d’autorisation au contrôleur qui reçoit des rappels à partir d’événements 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}."));
            }
        }
    }
}