Веб-хуки Центра партнеров
Область применения: Центр партнеров | Центр партнеров, управляемый 21Vianet | Центр партнеров для облака Microsoft для правительства США
соответствующие роли: администратор выставления счетов | агент администрирования | агент по продажам | агент службы поддержки
API-интерфейсы веб-перехватчика Центра партнеров позволяют партнерам регистрировать события изменения ресурсов. Эти события доставляются в форме HTTP POST-запросов на зарегистрированный URL-адрес партнера. Чтобы получить событие из Центра партнеров, партнеры размещают обратный вызов, в котором Центр партнеров может опубликовать событие изменения ресурса. Событие подписывается цифровой подписью, чтобы партнер смог убедиться, что он был отправлен из Центра партнеров. Уведомления Webhook активируются только в той среде, которая имеет последнюю конфигурацию для совместных продаж.
Центр партнеров поддерживает следующие события Webhook.
Обнаружено событие мошенничества Azure ("azure-fraud-event-detected")
Это событие возникает при обнаружении события мошенничества Azure.
Событие утверждения делегированных связей администратора ("dap-admin-relationship-approved")
Это событие инициируется, когда делегированные права администратора утверждены клиентской организацией.
Событие "Принятие клиентом сотрудничества с реселлером" ("reseller-relationship-accepted-by-customer")
Это событие возникает, когда клиент-арендатор одобряет отношения с торговым посредником.
Отношения косвенного торгового посредника, принятые клиентом (событие "отношения косвенного торгового посредника, принятые клиентом")
Это событие возникает, когда арендатор клиента подтверждает непрямые партнёрские отношения с реселлером.
Событие завершения делегированных отношений администрирования ("dap-admin-relationship-terminated")
Это событие возникает, когда клиент завершает делегированные права администратора.
Связь администратора Dap прекращена событием Майкрософт ("dap-admin-relationship-terminated-by-microsoft")
Это событие возникает, когда корпорация Майкрософт завершает DAP между партнером и клиентом после того, как DAP неактивна более 90 дней.
Событие активации детализации назначения доступа администратора ("granular-admin-access-assignment-activated")
Это событие происходит, когда партнер активирует гранулированное делегированное назначение административных привилегий после того, как роли Microsoft Entra назначены определенным группам безопасности.
Событие создания детализированного назначения доступа администратора ("granular-admin-access-assignment-created")
Это событие возникает, когда партнер создает назначение доступа для гранулярных делегированных административных привилегий. Партнеры могут назначать утвержденные клиентом роли Microsoft Entra определенным группам безопасности.
Удаление назначения детализированного доступа администратора ("granular-admin-access-assignment-deleted")
Это событие возникает, когда партнер удаляет назначение доступа к детализированным привилегиям делегированного администратора.
Обновление события назначения гранулярного доступа админа ("granular-admin-access-assignment-updated")
Это событие возникает, когда партнер обновляет назначение доступа с подробными делегированными административными привилегиями.
Событие активации гранулярного отношения администратора ("granular-admin-relationship-activated")
Это событие возникает, когда создаются и активируются гранулированные делегированные права администратора, которые клиент должен утвердить.
Событие одобрения гранулярного взаимодействия администратора ("granular-admin-relationship-approved")
Это событие возникает, когда клиентский арендатор утверждает детализированные права делегированного администратора.
Событие истечения полномочий подробного администратора ("истекли полномочия подробного администратора")
Это событие возникает при окончании действия гранулированных делегированных административных привилегий.
Событие создания подробного администраторского взаимодействия ("создание подробной администраторской связи")
Это событие возникает при создании гранулированных делегированных административных привилегий.
Событие обновления гранулярных администраторских отношений ("granular-admin-relationship-updated")
Это событие возникает, когда клиент или партнер обновляют гранулярные права делегированного администратора.
Гранулярное авторасширенное событие связи админа ("granular-admin-relationship-auto-extended")
Это событие происходит, когда система автоматически расширяет гранулированные права делегирования администратора.
Событие завершения отношений гранулярного администратора ("granular-admin-relationship-terminated")
Это событие возникает, когда партнер или клиент прекращает действие гранулярных делегированных административных прав.
Миграция на новую коммерческую модель завершена ("new-commerce-migration-completed")
Это событие создаётся при завершении миграции на новую коммерческую систему.
Новая коммерческая миграция создана ("new-commerce-migration-created")
Это событие возникает при создании новой коммерческой миграции.
Сбой миграции новой коммерческой платформы ("new-commerce-migration-failed")
Это событие возникает в случае сбоя новой коммерческой миграции.
Создание трансфера ("create-transfer")
Это событие возникает при создании передачи.
Передача обновлений ("update-transfer")
Это событие возникает при обновлении трансфера.
Полная передача ("полная передача")
Это событие возникает при завершении передачи.
Истечение срока действия передачи ("expire-transfer")
Это событие возникает при истечении срока действия передачи.
Сбой передачи ("отказ передачи")
Это событие возникает при сбое передачи.
Сбой расписания перехода на новую коммерческую систему ("new-commerce-migration-schedule-failed")
Событие возникает, когда расписание новой миграции в сфере торговли завершается неудачей.
Событие создания реферала ("создание-реферала")
Это событие возникает при создании ссылки.
Событие обновления реферала ("referral-updated")
Это событие возникает при обновлении ссылки.
Событие создания связанного реферала ("related-referral-created")
Это событие возникает при создании связанной ссылки.
Событие обновления связанной рекомендации ("related-referral-updated")
Это событие вызывается при обновлении связанной ссылки.
Активное событие подписки ("subscription-active")
Это событие возникает при активации подписки.
Примечание.
"Веб-перехватчик «Subscription Active» и соответствующее событие в журнале действий в настоящее время доступны только для тестовых арендаторов."
Событие ожидания подтверждения подписки ("подписка-в ожидании")
Это событие возникает, когда соответствующий заказ был успешно получен, и ожидается создание подписки.
Примечание.
Веб-хук ожидающей подписки и соответствующие события журнала действий доступны только для клиентов песочницы на данный момент.
Событие продления подписки ("продление подписки")
Это событие возникает, когда подписка продлевается.
Примечание.
Веб-перехватчик продления подписки и соответствующее событие в журнале действий доступны только для клиентов в среде песочницы.
Обновленное событие подписки (обновление подписки)
Это событие возникает при изменении подписки. Эти события создаются при наличии внутреннего изменения в дополнение к тому, когда изменения вносятся через API Центра партнеров.
Примечание.
Существует задержка до 48 часов между временем изменения подписки и активацией события обновления подписки.
Тестовое событие ("test-created")
Это событие позволяет самостоятельно подключиться и проверить регистрацию, запросить тестовое событие и отслеживать его ход выполнения. При попытке доставки события вы увидите сообщения об ошибках, полученные от Корпорации Майкрософт. Это ограничение применяется только к событиям, созданным тестом. Данные старше семи дней очищаются.
Превышение порогового значения ("usagerecords-thresholdExceed")
Это событие возникает, когда объем использования Microsoft Azure для любого клиента превышает бюджет расходов на использование (пороговое значение). Дополнительные сведения см. в разделе (Настройка бюджета расходов Azure для клиентов/ партнеров/set-an-azure-spending-budget-for-your-customers).
В будущем будут добавлены события вебхуков для ресурсов, которые находятся вне контроля партнёра, и будут предприняты дальнейшие обновления, чтобы сделать поступление этих событий максимально приближенным к реальному времени. Отзывы партнеров о том, какие события добавляют ценность для своего бизнеса, полезно определить, какие новые события следует добавить.
Для получения полного списка событий веб-перехватчика, поддерживаемых Партнерским центром, см. раздел События веб-перехватчика Партнерского центра.
Предварительные условия
- Учетные данные, описанные в аутентификации Центра партнеров. Этот сценарий поддерживает аутентификацию с использованием как самостоятельных учетных данных приложения, так и учетных данных приложения+пользователя.
Получение событий из Partner Center
Чтобы получать события из Центра партнеров, необходимо предоставить общедоступную конечную точку. Так как эта конечная точка открыта, необходимо убедиться, что передача данных идет из Центра партнеров. Все получаемые вами события Webhook имеют цифровую подпись, которая удостоверяется с цепочкой к корневому сертификату Microsoft. Также предоставляется ссылка на сертификат, используемый для подписывания события. Это позволяет обновить сертификат без необходимости повторного развертывания или перенастройки службы. Центр партнеров предпринимает 10 попыток передачи события. Если событие по-прежнему не доставляется после 10 попыток, оно перемещается в автономную очередь, и никаких дальнейших попыток доставки не предпринимается.
В следующем примере показано событие, размещенное в Центре партнеров.
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"
}
Примечание.
Заголовок авторизации имеет схему "Подпись". Это закодированная с помощью Base64 подпись содержимого.
Проверка подлинности обратного вызова
Чтобы выполнить проверку подлинности события обратного вызова, полученного из Центра партнеров, выполните следующие действия.
- Убедитесь, что необходимые заголовки присутствуют (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).
- Скачайте сертификат, используемый для подписания содержимого (x-ms-certificate-url).
- Проверьте цепочку сертификатов.
- Проверьте "Организацию" в сертификате.
- Перенесите содержимое с кодировкой UTF8 в буфер.
- Создайте поставщика шифрования RSA.
- Проверьте соответствие данных указанному алгоритму хэша (например, SHA256).
- Если проверка выполнена успешно, обработайте сообщение.
Примечание.
По умолчанию маркер подписи отправляется в заголовке авторизации. Если в вашей регистрации указано SignatureTokenToMsSignatureHeader как истинное, то маркер подписи отправляется в заголовке x-ms-signature.
Модель событий
В следующей таблице описаны свойства события Центра партнеров.
Свойства
Имя | Описание |
---|---|
EventName | Имя события. В форме {resource}-{action}. Например, "тест-создано". |
ResourceUri | URI ресурса, который изменился. |
Имя ресурса | Имя измененного ресурса. |
AuditUrl | Необязательно. URI записи аудита. |
ДатаИзмененияРесурсаUtc | Дата и время в формате UTC, когда произошло изменение ресурса. |
Пример
В следующем примере показана структура события Центра партнеров.
{
"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 вебхуков
Проверка подлинности
Все вызовы API веб-перехватчика проходят проверку подлинности с помощью токена Bearer в заголовке авторизации. Получение токена для доступа к https://api.partnercenter.microsoft.com
. Этот маркер является тем же маркером, который используется для доступа к остальным API Центра партнеров.
Получение списка событий
Возвращает список событий, которые в настоящее время поддерживаются API вебхуков.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration/events
Пример запроса
GET /webhooks/v1/registration/events
content-type: application/json
authorization: Bearer eyJ0e.......
accept: */*
host: api.partnercenter.microsoft.com
Пример ответа
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" ]
Регистрация для получения событий
Регистрирует арендатора для получения указанных событий.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Пример запроса
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"]
}
Пример ответа
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" ]
}
Посмотреть регистрацию
Возвращает регистрацию события вебхуков для арендатора.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Пример запроса
GET /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Пример ответа
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"]
}
Обновление регистрации событий
Обновляет существующую регистрацию событий.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Пример запроса
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"]
}
Пример ответа
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" ]
}
Отправка тестового события для проверки регистрации
Создает тестовое событие для проверки регистрации вебхуков. Этот тест предназначен для проверки того, что вы можете получать события из Центра партнеров. Данные для этих событий удаляются семь дней после создания первоначального события. Вы должны быть зарегистрированы для события "test-created" с использованием API регистрации, прежде чем отправлять событие валидации.
Примечание.
При публикации события проверки существует ограничение в 2 запроса в минуту.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents
Пример запроса
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:
Пример ответа
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" }
Проверьте, что событие доставлено
Возвращает текущее состояние события проверки. Эта проверка может быть полезной для устранения неполадок с доставкой событий. Ответ включает результат каждой попытки, сделанной для доставки события.
URL-адрес ресурса
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/{correlationId}
Пример запроса
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
Пример ответа
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"
}]
}
Пример проверки подписи
Пример подписи контроллера обратного вызова (ASP.NET)
[AuthorizeSignature]
[Route("webhooks/callback")]
public IHttpActionResult Post(PartnerResourceChangeCallBack callback)
Проверка подписи
В следующем примере показано, как добавить атрибут авторизации в контроллер, принимающий обратные вызовы от событий Webhook.
namespace Webhooks.Security
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using Microsoft.Partner.Logging;
/// <summary>
/// Signature based Authorization
/// </summary>
public class AuthorizeSignatureAttribute : AuthorizeAttribute
{
private const string MsSignatureHeader = "x-ms-signature";
private const string CertificateUrlHeader = "x-ms-certificate-url";
private const string SignatureAlgorithmHeader = "x-ms-signature-algorithm";
private const string MicrosoftCorporationIssuer = "O=Microsoft Corporation";
private const string SignatureScheme = "Signature";
/// <inheritdoc/>
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
ValidateAuthorizationHeaders(actionContext.Request);
await VerifySignature(actionContext.Request);
}
private static async Task<string> GetContentAsync(HttpRequestMessage request)
{
// By default the stream can only be read once and we need to read it here so that we can hash the body to validate the signature from microsoft.
// Load into a buffer, so that the stream can be accessed here and in the api when it binds the content to the expected model type.
await request.Content.LoadIntoBufferAsync();
var s = await request.Content.ReadAsStreamAsync();
var reader = new StreamReaders;
var body = await reader.ReadToEndAsync();
// set the stream position back to the beginning
if (s.CanSeek)
{
s.Seek(0, SeekOrigin.Begin);
}
return body;
}
private static void ValidateAuthorizationHeaders(HttpRequestMessage request)
{
var authHeader = request.Headers.Authorization;
if (string.IsNullOrWhiteSpace(authHeader?.Parameter) && string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, MsSignatureHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Authorization header missing."));
}
var signatureHeaderValue = GetHeaderValue(request.Headers, MsSignatureHeader);
if (authHeader != null
&& !string.Equals(authHeader.Scheme, SignatureScheme, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(signatureHeaderValue)
&& !signatureHeaderValue.StartsWith(SignatureScheme, StringComparison.OrdinalIgnoreCase))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Authorization scheme needs to be '{SignatureScheme}'."));
}
if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, CertificateUrlHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {CertificateUrlHeader} missing."));
}
if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, SignatureAlgorithmHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {SignatureAlgorithmHeader} missing."));
}
}
private static string GetHeaderValue(HttpHeaders headers, string key)
{
headers.TryGetValues(key, out var headerValues);
return headerValues?.FirstOrDefault();
}
private static async Task VerifySignature(HttpRequestMessage request)
{
// Get signature value from either authorization header or x-ms-signature header.
var base64Signature = request.Headers.Authorization?.Parameter ?? GetHeaderValue(request.Headers, MsSignatureHeader).Split(' ')[1];
var signatureAlgorithm = GetHeaderValue(request.Headers, SignatureAlgorithmHeader);
var certificateUrl = GetHeaderValue(request.Headers, CertificateUrlHeader);
var certificate = await GetCertificate(certificateUrl);
var content = await GetContentAsync(request);
var alg = signatureAlgorithm.Split('-'); // for example RSA-SHA1
var isValid = false;
var logger = GetLoggerIfAvailable(request);
// Validate the certificate
VerifyCertificate(certificate, request, logger);
if (alg.Length == 2 && alg[0].Equals("RSA", StringComparison.OrdinalIgnoreCase))
{
var signature = Convert.FromBase64String(base64Signature);
var csp = (RSACryptoServiceProvider)certificate.PublicKey.Key;
var encoding = new UTF8Encoding();
var data = encoding.GetBytes(content);
var hashAlgorithm = alg[1].ToUpper();
isValid = csp.VerifyData(data, CryptoConfig.MapNameToOID(hashAlgorithm), signature);
}
if (!isValid)
{
// log that we were not able to validate the signature
logger?.TrackTrace(
"Failed to validate signature for webhook callback",
new Dictionary<string, string> { { "base64Signature", base64Signature }, { "certificateUrl", certificateUrl }, { "signatureAlgorithm", signatureAlgorithm }, { "content", content } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Signature verification failed"));
}
}
private static ILogger GetLoggerIfAvailable(HttpRequestMessage request)
{
return request.GetDependencyScope().GetService(typeof(ILogger)) as ILogger;
}
private static async Task<X509Certificate2> GetCertificate(string certificateUrl)
{
byte[] certBytes;
using (var webClient = new WebClient())
{
certBytes = await webClient.DownloadDataTaskAsync(certificateUrl);
}
return new X509Certificate2(certBytes);
}
private static void VerifyCertificate(X509Certificate2 certificate, HttpRequestMessage request, ILogger logger)
{
if (!certificate.Verify())
{
logger?.TrackTrace("Failed to verify certificate for webhook callback.", new Dictionary<string, string> { { "Subject", certificate.Subject }, { "Issuer", certificate.Issuer } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Certificate verification failed."));
}
if (!certificate.Issuer.Contains(MicrosoftCorporationIssuer))
{
logger?.TrackTrace($"Certificate not issued by {MicrosoftCorporationIssuer}.", new Dictionary<string, string> { { "Issuer", certificate.Issuer } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Certificate not issued by {MicrosoftCorporationIssuer}."));
}
}
}
}