Поделиться через


Реализация вебхука в сервисе SaaS

При создании транзакционного предложения SaaS в Центре партнеров партнер предоставляет URL-адрес веб-перехватчика подключения , который будет использоваться в качестве конечной точки HTTP. Этот веб-перехватчик вызывается корпорацией Майкрософт с помощью HTTP-вызова POST для уведомления издателя о следующих событиях, происходящих на стороне Майкрософт:

Событие Webhook 1. При получении 2. Если принято 3. В случае отказа
ChangePlan Ответ с помощью HTTP 200 ПАТЧ с успехом (это событие является необязательным и автоматически принимается через 10 секунд) ПАТЧ при сбое ИЛИ ответ с помощью 4xx (в течение 10 секунд)
ChangeQuantity Ответ с помощью HTTP 200 ПАТЧ с успехом (это событие является необязательным и автоматически принимается через 10 секунд) ПАТЧ при сбое ИЛИ ответ с помощью 4xx (в течение 10 секунд)
Renew Ответ с помощью HTTP 200 Неприменимо Неприменимо
Suspend Ответ с помощью HTTP 200 Неприменимо Неприменимо
Unsubscribe Ответ с помощью HTTP 200 Неприменимо Неприменимо
Reinstate Ответ с помощью HTTP 200 Неприменимо Неприменимо (вызовите API удаления, чтобы активировать удаление, если восстановление не может быть принято)

Издатель должен внедрить веб-перехватчик в службу SaaS, чтобы состояние подписки SaaS соответствовало состоянию на стороне Майкрософт. Служба SaaS должна вызывать API Get Operation для проверки и авторизации вызова веб-перехватчика и данных полезных данных, прежде чем выполнять действия на основе уведомления веб-перехватчика. Издатель должен вернуть HTTP 200 в корпорацию Майкрософт сразу после обработки вызова веб-перехватчика. Это значение подтверждает, что вызов вебхука был успешно получен издателем.

Это важно

Служба URL-адресов веб-перехватчика должна быть запущена и работать 24 часа в сутки 7 дней в неделю и быть готовой к приему новых вызовов от корпорации Майкрософт в любое время. Корпорация Майкрософт имеет политику повторных попыток для вызова веб-перехватчика (500 повторных попыток в течение восьми часов), но если издатель не принимает вызов и не возвращает ответ, операция, о которой уведомляет веб-перехватчик, в конечном итоге завершится ошибкой на стороне Майкрософт.

Это важно

Независимые поставщики программного обеспечения должны избегать строгой десериализации схемы вебхука. Microsoft оставляет за собой право расширять схему в будущем.

Это важно

Независимые поставщики программного обеспечения должны проверить токен Microsoft Entra (токен JWT) в конечной точке веб-перехватчика из заголовка запроса. Это маркер стандартного носителя, который предоставляет независимым поставщикам программного обеспечения сведения о том, кто является вызывающим абонентом. Узнайте больше о том, как проверить токен, в этой статье. learn.microsoft.com/azure/active-directory/develop/access-tokens

Пример полезной нагрузки вебхука ChangePlan:

{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan2",
    "quantity": 10,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-10T18:48:58.4449937Z",
    "action": "ChangePlan",
    "status": "InProgress",
    "operationRequestSource": "Azure",
    "subscription":
    {
      "id": "<guid>",
      "name": "Test",
      "publisherId": "XXX",
      "offerId": "YYY",
      "planId": "plan1",
      "quantity": 10,
      "beneficiary":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "allowedCustomerOperations": ["Delete", "Update", "Read"],
      "sessionMode": "None",
      "isFreeTrial": false,
      "isTest": false,
      "sandboxType": "None",
      "saasSubscriptionStatus": "Subscribed",
      "term":
        {
          "startDate": "2022-02-10T00:00:00Z",
          "endDate": "2022-03-12T00:00:00Z",
          "termUnit": "P1M",
          "chargeDuration": null,
        },
      "autoRenew": true,
      "created": "2022-01-10T23:15:03.365988Z",
      "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
    "purchaseToken": null
}

Пример полезной нагрузки вебхука для события ChangeQuantity:


{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan1",
    "quantity": 20,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-10T18:54:00.6158973Z",
    "action": "ChangeQuantity",
    "status": "InProgress",
    "operationRequestSource": "Azure",
    "subscription": {
        "id": "<guid>",
        "name": "Test",
        "publisherId": "XXX",
        "offerId": "YYY",
        "planId": "plan1",
        "quantity": 10,
        "beneficiary":
            {
            "emailId": [email protected],
            "objectId": "<guid>",
            "tenantId": "<guid>",
            "puid": "1234567890",
            },
        "purchaser":
            {
            "emailId": [email protected],
            "objectId": "<guid>",
            "tenantId": "<guid>",
            "puid": "1234567890",
            },
        "allowedCustomerOperations": ["Delete", "Update", "Read"],
        "sessionMode": "None",
        "isFreeTrial": false,
        "isTest": false,
        "sandboxType": "None",
        "saasSubscriptionStatus": "Subscribed",
        "term":
            {
            "startDate": "2022-02-10T00:00:00Z",
            "endDate": "2022-03-12T00:00:00Z",
            "termUnit": "P1M",
            "chargeDuration": null,
            },
        "autoRenew": true,
        "created": "2022-01-10T23:15:03.365988Z",
        "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
    "purchaseToken": null
}

Пример полезной нагрузки вебхука для события восстановления подписки:

// end user's payment instrument became valid again, after being suspended, and the SaaS subscription is being reinstated


{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan1",
    "quantity": 100,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-11T11:38:10.3508619Z",
    "action": "Reinstate",
    "status": "InProgress",
    "operationRequestSource": "Azure",
    "subscription":
    {
      "id": "<guid>",
      "name": "Test",
      "publisherId": "XXX",
      "offerId": "YYY",
      "planId": "plan1",
      "quantity": 100,
      "beneficiary":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "allowedCustomerOperations": ["Delete", "Update", "Read"],
      "sessionMode": "None",
      "isFreeTrial": false,
      "isTest": false,
      "sandboxType": "None",
      "saasSubscriptionStatus": "Suspended",
      "term":
        {
          "startDate": "2022-02-10T00:00:00Z",
          "endDate": "2022-03-12T00:00:00Z",
          "termUnit": "P1M",
          "chargeDuration": null,
        },
      "autoRenew": true,
      "created": "2022-01-10T23:15:03.365988Z",
      "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
    "purchaseToken": null
}
 

Пример полезной нагрузки вебхука для события renew:

// end user's subscription renewal
 
{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan1",
    "quantity": 100,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-10T08:49:01.8613208Z",
    "action": "Renew",
    "status": "Succeeded",
    "operationRequestSource": "Azure",
    "subscription":
    {
      "id": "<guid>",
      "name": "Test",
      "publisherId": "XXX",
      "offerId": "YYY",
      "planId": "plan1",
      "quantity": 100,
      "beneficiary":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "allowedCustomerOperations": ["Delete", "Update", "Read"],
      "sessionMode": "None",
      "isFreeTrial": false,
      "isTest": false,
      "sandboxType": "None",
      "saasSubscriptionStatus": "Subscribed",
      "term":
        {
          "startDate": "2022-02-10T00:00:00Z",
          "endDate": "2022-03-12T00:00:00Z",
          "termUnit": "P1M",
          "chargeDuration": null,
        },
      "autoRenew": true,
      "created": "2022-01-10T23:15:03.365988Z",
      "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
  "purchaseToken": null,
}

Пример полезной нагрузки вебхука для события приостановки:


{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan1",
    "quantity": 100,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-10T08:49:01.8613208Z",
    "action": "Suspend",
    "status": "Succeeded",
    "operationRequestSource": "Azure",
    "subscription":
    {
      "id": "<guid>",
      "name": "Test",
      "publisherId": "XXX",
      "offerId": "YYY",
      "planId": "plan1",
      "quantity": 100,
      "beneficiary":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "allowedCustomerOperations": ["Delete", "Update", "Read"],
      "sessionMode": "None",
      "isFreeTrial": false,
      "isTest": false,
      "sandboxType": "None",
      "saasSubscriptionStatus": "Suspended",
      "term":
        {
          "startDate": "2022-02-10T00:00:00Z",
          "endDate": "2022-03-12T00:00:00Z",
          "termUnit": "P1M",
          "chargeDuration": null,
        },
      "autoRenew": true,
      "created": "2022-01-10T23:15:03.365988Z",
      "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
  "purchaseToken": null,
}

Пример полезной нагрузки вебхука для события отписки:

Это событие доступно только для уведомлений. Для этого события нет отправки в ACK.


{
    "id": "<guid>",
    "activityId": "<guid>",
    "publisherId": "XXX",
    "offerId": "YYY",
    "planId": "plan1",
    "quantity": 100,
    "subscriptionId": "<guid>",
    "timeStamp": "2023-02-10T08:49:01.8613208Z",
    "action": "Unsubscribe",
    "status": "Succeeded",
    "operationRequestSource": "Azure",
    "subscription":
    {
      "id": "<guid>",
      "name": "Test",
      "publisherId": "XXX",
      "offerId": "YYY",
      "planId": "plan1",
      "quantity": 100,
      "beneficiary":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": [email protected],
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "allowedCustomerOperations": ["Delete", "Update", "Read"],
      "sessionMode": "None",
      "isFreeTrial": false,
      "isTest": false,
      "sandboxType": "None",
      "saasSubscriptionStatus": "Unsubscribed",
      "term":
        {
          "startDate": "2022-02-10T00:00:00Z",
          "endDate": "2022-03-12T00:00:00Z",
          "termUnit": "P1M",
          "chargeDuration": null,
        },
      "autoRenew": true,
      "created": "2022-01-10T23:15:03.365988Z",
      "lastModified": "2022-02-14T20:26:04.5632549Z",
    },
  "purchaseToken": null,
}

Защита вебхуков

Вы должны защитить свои веб-перехватчики, чтобы никто, кроме конечных точек Майкрософт, не выполнял такие вызовы веб-перехватчиков. Вы можете использовать любую технологию для реализации вебхуков, однако ваша реализация вебхука должна соответствовать следующим рекомендациям по безопасности (см. Руководство).

  • Корпорация Майкрософт вызывает веб-перехватчики с заголовками авторизации, которые содержат необходимые сведения для проверки вызовов. Необходимо включить веб-перехватчики, чтобы они могли получать заголовки авторизации. (Не добавляйте сведения об авторизации или маркеры безопасности, такие как токены SAS, непосредственно в URL-адреса веб-перехватчиков. Такие веб-перехватчики могут не получить заголовки авторизации, которые корпорация Майкрософт отправляет при вызове ваших веб-перехватчиков.

  • Маркер JWT Bearer, передаваемый в заголовке авторизации, содержит следующие данные в полезных данных, которые можно использовать для защиты конечных точек.

  • "aud": "это идентификатор приложения Microsoft Entra Identity, который вы добавляете в техническую конфигурацию вашего предложения в Центре партнеров Microsoft"

  • "appid" или "azp": это идентификатор ресурса, который вы используете при создании маркера авторизации издателя для вызова API выполнения SaaS. И в зависимости от настройки приложения вы можете увидеть значение идентификатора ресурса в "appid" или "azp". Токен имеет одно из двух утверждений, и вы должны реагировать соответствующим образом в своем коде.

  • "tid": "это идентификатор клиента Microsoft Entra, который вы добавляете в техническую конфигурацию вашего предложения в Центре партнеров Майкрософт"

  • Вы можете проверить вышеуказанные поля, чтобы убедиться, что вызов Webhook действителен.

Это важно

Корпорация Майкрософт начнет требовать от независимых поставщиков программного обеспечения создавать свои веб-перехватчики безопасным способом и принимать заголовки авторизации. Если ваша текущая реализация вебхука не может принимать заголовки авторизации, вам необходимо обновить вебхуки и защитить такие конечные точки (используя приведенные выше рекомендации), чтобы избежать каких-либо сбоев.

Разработка и тестирование

Чтобы начать процесс разработки, мы рекомендуем создавать фиктивные ответы API на стороне издателя. Эти ответы могут быть основаны на примерах ответов, приведенных в этой статье.

Когда издатель будет готов к сквозному тестированию:

  • Опубликуйте предложение SaaS для ограниченной аудитории предварительной версии и оставьте его на стадии предварительного просмотра.
  • Установите цену плана равной нулю, чтобы избежать фактических расходов на выставление счетов во время тестирования. Другой вариант — установить ненулевую цену и отменить все контрольные закупки в течение 24 часов.
  • Убедитесь, что все потоки вызываются от начала до конца, чтобы смоделировать реальный сценарий клиента.
  • Если партнер хочет протестировать весь процесс покупки и выставления счетов, сделайте это с помощью предложения по цене выше 0 долларов США. За покупку выставляется счет, и будет выставлен счет.

Поток покупки может быть активирован на портале Azure или на сайтах Microsoft AppSource в зависимости от того, где публикуется предложение.

Действия по изменению плана, изменению количества и отписке тестируются со стороны вебмастера. Со стороны Майкрософт отмена подписки может быть активирована как на портале Azure, так и в Центре администрирования (портале, на котором осуществляется управление покупками Microsoft AppSource). Изменение количества и плана может быть активировано только из Центра администрирования.

Получите поддержку

Варианты поддержки издателей см. в разделе Поддержка программы коммерческой платформы в Центре партнеров .