Webhooks de l’Espace partenairesPartner Center webhooks

S’applique àApplies To

  • Espace partenairesPartner Center
  • Espace partenaires géré par 21VianetPartner Center operated by 21Vianet
  • Espace partenaires de Microsoft Cloud GermanyPartner Center for Microsoft Cloud Germany
  • Espace partenaires de Microsoft Cloud for US GovernmentPartner Center for Microsoft Cloud for US Government

Les API de webhook de l’espace partenaires permettent aux partenaires de s’inscrire aux événements de changement de ressource.The Partner Center Webhook APIs allow partners to register for resource change events. Ces événements sont fournis sous la forme de publications HTTP à l’URL inscrite du partenaire.These events are delivered in the form of HTTP POSTs to the partner's registered URL. Pour recevoir un événement de l’espace partenaires, les partenaires hébergent un rappel où l’espace partenaires peut poster l’événement de changement de ressource.To receive an event from Partner Center, partners will host a callback where Partner Center can POST the resource change event. L’événement est signé numériquement afin que le partenaire puisse vérifier qu’il a été envoyé à partir de l’espace partenaires.The event will be digitally signed so that the partner can verify that it was sent from Partner Center.

Les partenaires peuvent sélectionner des événements de webhook, comme dans les exemples suivants, qui sont pris en charge par l’espace partenaires.Partners can select from Webhook events, like the following examples, that are supported by Partner Center.

  • Événement de test (« test-created »)Test Event ("test-created")

    Cet événement vous permet d’auto-intégrer et de tester votre inscription en demandant un événement de test, puis en effectuant le suivi de sa progression.This event allows you to self-onboard and test your registration by requesting a test event and then tracking its progress. Vous pouvez voir les messages d’erreur reçus de Microsoft lors de la tentative de remise de l’événement.You can see the failure messages that are being received from Microsoft while trying to deliver the event. Cette restriction s’applique uniquement aux événements « test créés ».This restriction only applies to "test-created" events. Les données datant de plus de sept jours seront purgées.Data older than seven days will be purged.

  • Événement de mise à jour d’abonnement (« subscription-updated »)Subscription Updated Event ("subscription-updated")

    Cet événement se déclenche lors d’une modification de l’abonnement.This event is raised when the subscription changes. Ces événements seront générés lorsqu’il se produit un changement interne en plus des modifications apportées par le biais de l’API de l’Espace partenaires.These events will be generated when there is an internal change in addition to when changes are made through the Partner Center API.

    Notes

    Il y a un délai de 48 heures entre le moment où un abonnement est modifié et le moment où l’événement mis à jour de l’abonnement est déclenché.There is a delay of up to 48 hours between the time a subscription changes and when the Subscription Updated event is triggered.

  • Événement de dépassement de seuil (« usagerecords-thresholdExceeded »)Threshold Exceeded Event ("usagerecords-thresholdExceeded")

    Cet événement se déclenche lorsque la quantité d’utilisation de Microsoft Azure d’un client dépasse son budget de dépenses d’utilisation (son seuil).This event is raised when the amount of Microsoft Azure usage for any customer exceeds their usage spending budget (their threshold). Pour plus d’informations, consultez [définir un budget de dépenses Azure pour vos clients/Partner-Center/Set-an-Azure-Pass-budget-for-your-clients).For more information, see [Set an Azure spending budget for your customers/partner-center/set-an-azure-spending-budget-for-your-customers).

  • Événement créé par la référence (« référence créée »)Referral Created Event ("referral-created")

    Cet événement est déclenché lorsque la référence est créée.This event is raised when the referral is created.

  • Événement mis à jour de référence (« référence-mis à jour »)Referral Updated Event ("referral-updated")

    Cet événement est déclenché lorsque la référence est mise à jour.This event is raised when the referral is updated.

  • Événement de facturation prête (« prêt pour la facturation »)Invoice Ready Event ("invoice-ready")

    Cet événement est déclenché lorsque la nouvelle facture est prête.This event is raised when the new invoice is ready.

Les événements de webhook ultérieurs seront ajoutés pour les ressources qui changent dans le système dont le partenaire n’est pas en mesure de contrôler, et d’autres mises à jour seront effectuées pour que ces événements soient aussi proches que possible en temps réel.Future Webhook events will be added for resources that change in the system that the partner isn't in control of, and further updates will be made to get those events as close to "real time" as possible. Les commentaires des partenaires sur lesquels les événements ajoutent de la valeur à leur entreprise seront utiles pour déterminer les nouveaux événements à ajouter.Feedback from Partners on which events add value to their business will be useful in determining what new events to add.

Pour obtenir la liste complète des événements webhook pris en charge par l’espace partenaires, consultez événements de webhook de l’espace partenaires.For a complete list of Webhook events supported by Partner Center, see Partner Center webhook events.

PrérequisPrerequisites

  • Informations d’identification, comme décrit dans Authentification auprès de l’Espace partenaires.Credentials as described in Partner Center authentication. Ce scénario prend en charge l’authentification avec les informations d’identification de l’application autonome et de l’application + utilisateur.This scenario supports authentication with both standalone App and App+User credentials.

Réception d’événements de l’espace partenairesReceiving events from Partner Center

Pour recevoir des événements de l’espace partenaires, vous devez exposer un point de terminaison accessible publiquement.To receive events from Partner Center, you must expose a publicly accessible endpoint. Étant donné que ce point de terminaison est exposé, vous devez vérifier que la communication est établie à partir de l’espace partenaires.Because this endpoint is exposed, you must validate that the communication is from Partner Center. Tous les événements de webhook que vous recevez sont signés numériquement avec un certificat qui est lié à la racine Microsoft.All Webhook events that you receive are digitally signed with a certificate that chains to the Microsoft Root. Un lien vers le certificat utilisé pour signer l’événement est également fourni.A link to the certificate used to sign the event will also be provided. Cela permet de renouveler le certificat sans avoir à redéployer ou reconfigurer votre service.This will allow the certificate to be renewed without you having to redeploy or reconfigure your service. L’espace partenaires fera 10 tentatives pour remettre l’événement.Partner Center will make 10 attempts to deliver the event. 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 au moment de la remise.If the event is still not delivered after 10 attempts, it will be moved into an offline queue and no further attempts will be made at delivery.

L’exemple suivant montre un événement publié par l’espace partenaires.The following sample shows an event posted from Partner Center.

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

Notes

L’en-tête d’autorisation a un schéma de « signature ».The Authorization header has a scheme of "Signature". Il s’agit d’une signature encodée en base64 du contenu.This is a base64 encoded signature of the content.

Comment authentifier le rappelHow to authenticate the callback

Pour authentifier l’événement de rappel reçu à partir de l’espace partenaires, procédez comme suit :To authenticate the callback event received from Partner Center, follow these steps:

  1. Vérifiez que les en-têtes requis sont présents (autorisation, x-ms-Certificate-URL, x-ms-signature-algorithme).Verify the required headers are present (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).

  2. Téléchargez le certificat utilisé pour signer le contenu (x-ms-certificate-url).Download the certificate used to sign the content (x-ms-certificate-url).

  3. Vérifiez la chaîne de certificats.Verify the Certificate Chain.

  4. Vérifiez l' « organisation » du certificat.Verify the "Organization" of the certificate.

  5. Lit le contenu avec encodage UTF8 dans une mémoire tampon.Read the content with UTF8 encoding into a buffer.

  6. Créez un fournisseur de chiffrement RSA.Create an RSA Crypto Provider.

  7. Vérifiez que les données correspondent à ce qui a été signé avec l’algorithme de hachage spécifié (par exemple SHA256).Verify the data matches what was signed with the specified hash algorithm (for example SHA256).

  8. Si la vérification est réussie, traitez le message.If the verification succeeds, process the message.

Notes

Par défaut, le jeton de signature est envoyé dans un en-tête d’autorisation.By default, the signature token will be sent in an Authorization header. Si vous affectez à SignatureTokenToMsSignatureHeader la valeur true dans votre inscription, le jeton de signature est envoyé dans l’en-tête x-ms-signature à la place.If you set SignatureTokenToMsSignatureHeader to true in your registration, the signature token will be sent in the x-ms-signature header instead.

Modèle d’événementEvent model

Le tableau suivant décrit les propriétés d’un événement de l’espace partenaires.The following table describes the properties of a Partner Center event.

PropriétésProperties

NomName DescriptionDescription
EventNameEventName Nom de l’événement.The name of the event. Sous la forme {Resource}-{action}.In the form {resource}-{action}. Par exemple, « test créé ».For example, "test-created".
URIResourceUri URI de la ressource qui a été modifiée.The URI of the resource that changed.
ResourceNameResourceName Nom de la ressource qui a été modifiée.The name of the resource that changed.
AuditUrlAuditUrl Optionnel.Optional. URI de l’enregistrement d’audit.The URI of the Audit record.
ResourceChangeUtcDateResourceChangeUtcDate Date et heure, au format UTC, auxquelles la modification de ressource s’est produite.The date and time, in UTC format, when the resource change occurred.

ExempleSample

L’exemple suivant illustre la structure d’un événement de l’espace partenaires.The following sample shows the structure of a Partner Center event.

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

AuthentificationAuthentication

Tous les appels aux API webhook sont authentifiés à l’aide du jeton de porteur dans l’en-tête Authorization.All calls to the Webhook APIs are authenticated using the Bearer token in the Authorization Header. Obtenir un jeton d’accès pour accéder à https://api.partnercenter.microsoft.com .Acquire an access token to access 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.This token is the same token that is used to access the rest of the Partner Center APIs.

Obtenir une liste d’événementsGet a list of events

Retourne une liste des événements actuellement pris en charge par les API webhook.Returns a list of the events that are currently supported by the Webhook APIs.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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

Exemple de réponseResponse example

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

S’inscrire pour recevoir des événementsRegister to receive events

Inscrit un locataire pour recevoir les événements spécifiés.Registers a tenant to receive the specified events.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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éponseResponse example

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

Afficher une inscriptionView a registration

Retourne l’inscription de l’événement webhooks pour un locataire.Returns the Webhooks event registration for a tenant.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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

Exemple de réponseResponse example

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

Mettre à jour une inscription d’événementUpdate an event registration

Met à jour une inscription d’événement existante.Updates an existing event registration.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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éponseResponse example

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

Envoyer un événement de test pour valider votre inscriptionSend a test event to validate your registration

Génère un événement de test pour valider l’inscription d’un webhook.Generates a test event to validate the Webhooks registration. Ce test est destiné à confirmer que vous pouvez recevoir des événements de l’espace partenaires.This test is intended to validate that you can receive events from Partner Center. Les données de ces événements seront supprimées sept jours après la création de l’événement initial.Data for these events will be deleted seven days after the initial event is created. Vous devez être inscrit pour l’événement « test créé », à l’aide de l’API d’inscription, avant d’envoyer un événement de validation.You must be registered for the "test-created" event, using the registration API, before sending a validation event.

Notes

Il existe une limite de 2 requêtes par minute lors de la publication d’un événement de validation.There is a throttle limit of 2 requests per minute when posting a validation event.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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:

Exemple de réponseResponse example

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

Vérifier que l’événement a été remisVerify that the event was delivered

Retourne l’état actuel de l’événement de validation.Returns the current state of the validation event. Cette vérification peut être utile pour résoudre les problèmes de remise des événements.This verification can be helpful for troubleshooting event delivery issues. La réponse contient un résultat pour chaque tentative effectuée pour remettre l’événement.The Response contains a result for each attempt that is made to deliver the event.

URL des ressourcesResource URL

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

Exemple de requêteRequest example

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

Exemple de réponseResponse example

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

Exemple de validation de signatureExample for Signature Validation

Exemple de signature de contrôleur de rappel (ASP.NET)Sample Callback Controller signature (ASP.NET)

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

Validation de signatureSignature Validation

L’exemple suivant montre comment ajouter un attribut d’autorisation au contrôleur qui reçoit des rappels d’événements de webhook.The following example shows how to add an Authorization Attribute to the controller that is receiving callbacks from Webhook events.

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