SaaS サービスでの Webhook の実装

パートナー センターで取引可能な SaaS オファーを作成する場合、パートナーは、HTTP エンドポイントとして使用される接続 Webhook の URL を指定します。 この Webhook は、Microsoft 側で発生する次のイベントを公開元に通知するために、POST HTTP 呼び出しを使用して Microsoft によって呼び出されます。

Webhook イベント 1. 受信した場合 2. 承諾された場合 3. 拒否された場合
ChangePlan HTTP 200 で応答する 成功した PATCH (このイベントは省略可能で、10 秒で自動受け取り) PATCH with failure OR respond with 4xx (within 10 seconds) (PATCH with failure OR respond with 4xx (10 秒以内)
ChangeQuantity HTTP 200 で応答する 成功した PATCH (このイベントは省略可能で、10 秒で自動受け取り) PATCH with failure OR respond with 4xx (within 10 seconds) (PATCH with failure OR respond with 4xx (10 秒以内)
Renew HTTP 200 で応答する 適用なし 適用なし
Suspend HTTP 200 で応答する 適用なし 適用なし
Unsubscribe HTTP 200 で応答する 適用なし 適用なし
Reinstate HTTP 200 で応答する 適用なし 適用できません (再開を受け入れることができない場合に削除をトリガーする delete API を呼び出す)

発行者は、SaaS サブスクリプションの状態を Microsoft 側と一貫性のある状態に保つために、SaaS サービスに Webhook を実装する必要があります。 SaaS サービスでは、Webhook 通知に基づいてアクションを実行する前に、Get Operation API を呼び出して、Webhook 呼び出しとペイロード データを検証および承認する必要があります。 Webhook 呼び出しが処理されるとすぐに、発行者から Microsoft に HTTP 200 を返す必要があります。 この値は、Webhook 呼び出しが発行者によって正常に受信されたことを確認します。

重要

Webhook URL サービスは、24 x 7 で稼働しており、常に Microsoft から新しい呼び出しを受け取る準備ができている必要があります。 Microsoft には Webhook 呼び出しの再試行ポリシーがあります (8 時間にわたって 500 回の再試行)、発行元が呼び出しを受け入れて応答を返さない場合、Webhook が通知する操作は最終的に Microsoft 側で失敗します。

重要

ISV では、Webhook スキーマの厳密な逆シリアル化を回避する必要があります。 Microsoft は、将来スキーマを拡張する権利を留保します。

重要

ISV は、要求ヘッダーから Webhook エンドポイント上の Microsoft Entra Token (JWT トークン) を検証する必要があります。 これは標準的なベアラー トークンであり、呼び出し元のユーザーに関する ISV の詳細を提供します。 トークンを検証する方法の詳細については、この記事を参照してください。 learn.microsoft.com/azure/active-directory/develop/access-tokens

ChangePlan の Webhook ペイロードの例:

{
    "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": XX@outlook.com,
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": XX@outlook.com,
          "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 イベントの Webhook ペイロードの例:


{
    "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": XX@outlook.com,
            "objectId": "<guid>",
            "tenantId": "<guid>",
            "puid": "1234567890",
            },
        "purchaser":
            {
            "emailId": XX@outlook.com,
            "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
}

サブスクリプションの再開イベントの Webhook ペイロードの例:

// 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": XX@outlook.com,
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": XX@outlook.com,
          "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
}
 

更新イベントの Webhook ペイロードの例:

// 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": XX@outlook.com,
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": XX@outlook.com,
          "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,
}

中断イベントの Webhook ペイロードの例:


{
    "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": XX@outlook.com,
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": XX@outlook.com,
          "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,
}

サブスクライブ解除イベントの Webhook ペイロードの例:

これは通知専用のイベントです。 このイベントの 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": XX@outlook.com,
          "objectId": "<guid>",
          "tenantId": "<guid>",
          "puid": "1234567890",
        },
      "purchaser":
        {
          "emailId": XX@outlook.com,
          "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,
}

Webhook のセキュリティ保護

Microsoft エンドポイント以外のユーザーがこのような Webhook 呼び出しを行っていないように、Webhook をセキュリティで保護する必要があります。 任意のテクノロジを使用して Webhook を実装できますが、Webhook の実装は次のセキュリティ ガイドラインに従う必要があります。

  • Microsoft は、呼び出しを検証するために必要な情報を含む Authorization ヘッダーを使用して Webhook を呼び出します。 承認ヘッダーを受信できるようにするには、Webhook を有効にする必要があります。 (Webhook URL に直接、承認の詳細や SAS トークンなどのセキュリティ トークンを追加しないでください。このような Webhook は、Webhook を呼び出すときに Microsoft が送信する Authorization ヘッダーの取得に失敗する可能性があります)。

  • Authorization ヘッダーで渡される JWT Bearer トークンには、エンドポイントをセキュリティで保護するために使用できるペイロードに次のデータが含まれています。

  • "aud": "これは、Microsoft パートナー センターでオファーの技術構成に追加する Microsoft Entra Identity アプリケーション ID です"

  • "appid" または "azp": これは、SaaS フルフィルメント API を呼び出す発行元承認トークンを作成するときに使用するリソース ID です。 また、アプリケーションのセットアップによっては、"appid" または "azp" のいずれかにこのリソース ID 値が表示される場合があります。 トークンには 2 つの要求のいずれかが含まれており、コード内でそれに応じて対応する必要があります。

  • "tid": "これは、Microsoft パートナー センターでオファーの技術構成に追加する Microsoft Entra テナント ID です"

  • 上記の渡されたフィールドに対してチェックして、Webhook 呼び出しが有効であることを確認できます。

重要

Microsoft では、セキュリティで保護された方法で Webhook を作成し、承認ヘッダーを受け入れるように ISV に要求を開始します。 現在の Webhook 実装で承認ヘッダーを受け入れることができない場合は、中断を回避するために、Webhook を更新し、(上記のガイドラインを使用して) そのようなエンドポイントをセキュリティで保護する必要があります。

開発とテスト

開発プロセスを開始するには、発行者側でダミー API 応答を作成することをお勧めします。 これらの応答は、この記事に記載されているサンプル応答に基づくことができます。

発行者がエンドツーエンド テストの準備ができたら、次のようになります。

  • SaaS オファーを限られたプレビュー対象ユーザーに公開し、プレビュー段階のままにします。
  • テスト中に実際の請求費用が発生しないように、プラン価格を 0 に設定します。 もう 1 つのオプションは、0 以外の価格を設定し、24 時間以内にすべてのテスト購入をキャンセルすることです。
  • 実際の顧客のシナリオをシミュレートするために、すべてのフローがエンド ツー エンドで呼び出されていることを確認します。
  • パートナーが購入および請求の完全なフローをテストする必要がある場合は、0 ドルより高い価格のオファーを使用して、それを行ってください。 購入が課金され、請求書が生成されます。

購入フローは、オファーが公開されている場所に応じて、Azure portal または Microsoft AppSource サイトからトリガーできます。

"プラン変更"、"数量変更"、"登録解除" の各アクションは、発行者側からテストされます。 Microsoft 側からは、Azure portal と管理センター (Microsoft AppSource での購入が管理されているポータル) の両方から、"登録解除" をトリガーできます。 "数量とプランの変更" は、管理センターからのみ実行できます。

サポートを受ける

発行者サポート オプションについては、「パートナー センターでの商業マーケットプレース プログラムのサポート」を参照してください。

次のステップ