Implementieren eines Webhooks für den SaaS-Dienst

Beim Erstellen eines transaktionsfähigen SaaS-Angebots im Partner Center stellt der Partner die URL des Verbindungswebhooks bereit, die als HTTP-Endpunkt verwendet werden soll. Dieser Webhook wird von Microsoft mithilfe des HTTP-Aufrufs POST aufgerufen, um die Herausgeberseite über die folgenden Ereignissen zu informieren, die auf Microsoft-Seite auftreten:

Webhook-Ereignis 1. Wann empfangen 2. Wenn akzeptiert 3. Wenn abgelehnt
ChangePlan Antworten mit HTTP 200 PATCH mit Erfolg (dieses Ereignis ist optional und automatisch in 10 Sek.) PATCH mit Fehler ODER reagiert mit 4xx (innerhalb von 10 Sekunden)
ChangeQuantity Antworten mit HTTP 200 PATCH mit Erfolg (dieses Ereignis ist optional und automatisch in 10 Sek.) PATCH mit Fehler ODER reagiert mit 4xx (innerhalb von 10 Sekunden)
Renew Antworten mit HTTP 200 Nicht zutreffend Nicht zutreffend
Suspend Antworten mit HTTP 200 Nicht zutreffend Nicht zutreffend
Unsubscribe Antworten mit HTTP 200 Nicht zutreffend Nicht zutreffend
Reinstate Antworten mit HTTP 200 Nicht zutreffend Nicht zutreffend (Aufruflösch-API zum Auslösen des Löschens, wenn die Wiederaufnahme nicht akzeptiert werden kann)

Der Herausgeber muss einen Webhook im SaaS-Dienst implementieren, um den Status des SaaS-Abonnements mit der Microsoft-Seite konsistent zu halten. Der SaaS-Dienst muss die API zum Abrufen des Vorgangs aufrufen, um den Webhookaufruf und die Nutzlastdaten zu überprüfen und zu autorisieren, bevor Aktionen auf Grundlage der Webhookbenachrichtigung durchgeführt werden. Der Herausgeber sollte eine HTTP-200-Antwort an Microsoft zurückgeben, sobald der Webhookaufruf verarbeitet wird. Dieser Wert bestätigt, dass der Webhookaufruf vom Herausgeber erfolgreich empfangen wurde.

Wichtig

Der Webhook-URL-Dienst muss 24 x 7 ausgeführt werden und kann jederzeit neue Anrufe von Microsoft empfangen. Microsoft verfügt über eine Wiederholungsrichtlinie für den Webhook-Aufruf (500 Wiederholungsversuche über acht Stunden), aber wenn der Herausgeber den Anruf nicht akzeptiert und eine Antwort zurückgibt, schlägt der Vorgang, über den Webhook benachrichtigt wird, schließlich fehl auf der Microsoft-Seite.

Wichtig

ISVs sollten eine strikte Deserialisierung des Webhook-Schemas vermeiden. Microsoft behält sich das Recht vor, das Schema zukünftig zu erweitern.

Wichtig

ISVs müssen das Microsoft Entra-Token (JWT-Token) auf ihrem Webhook-Endpunkt aus dem Anforderungsheader überprüfen. Dies ist ein Standard-Bearertoken und gibt ISV-Details darüber, wer der Anrufer ist. Erfahren Sie mehr darüber, wie Sie das Token in diesem Artikel überprüfen. learn.microsoft.com/azure/active-directory/develop/access-tokens

Beispiel für Webhook-Nutzdaten von ChangePlan (Plan ändern):

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

Beispiel für Webhook-Nutzdaten von ChangeQuantity-Ereignis (Menge ändern):


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

Beispiel für die Webhook-Nutzlast eines Abonnement-Neustatusereignisses:

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

Beispiel für eine Webhook-Nutzlast eines Verlängerungsereignisses:

// 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,
}

Beispiel für eine Webhook-Nutzlast eines Anhalteereignisses:


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

Beispiel für eine Webhook-Nutzlast eines Abonnementereignisses:

Dies ist ein Nur-Benachrichtigungsereignis. Für dieses Ereignis ist kein Senden an ACK vorhanden.


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

Schützen von Webhooks

Sie müssen Ihre Webhooks schützen, damit niemand anderes als Microsoft-Endpunkte solche Webhook-Aufrufe tätigen. Sie können jede Technologie verwenden, um Ihre Webhooks zu implementieren, ihre Webhook-Implementierung muss jedoch den folgenden Sicherheitsrichtlinien entsprechen.

  • Microsoft ruft Ihre Webhooks mit Autorisierungsheadern auf, die erforderliche Informationen zum Überprüfen der Aufrufe enthalten. Sie müssen Ihre Webhooks aktivieren, um die Autorisierungsheader empfangen zu können. (Fügen Sie keine Autorisierungsdetails oder Sicherheitstoken wie SAS-Token direkt in den Webhook-URLs hinzu. Solche Webhooks können die Autorisierungsheader, die Microsoft beim Aufrufen Ihrer Webhooks sendet, nicht abrufen.

  • Das im Autorisierungsheader übergebene JWT Bearer-Token enthält die folgenden Daten in der Nutzlast, die Sie zum Sichern Ihrer Endpunkte verwenden können.

  • "aud": "Dies ist die Microsoft Entra Identity-Anwendungs-ID, die Sie zur technischen Konfiguration Ihres Angebots im Microsoft Partner Center hinzufügen"

  • "appid" oder "azp": Dies ist die Ressourcen-ID, die Sie verwenden, wenn Sie Publisher-Autorisierungstoken zum Aufrufen von SaaS-Erfüllungs-APIs erstellen. Je nach Anwendungssetup wird dieser Ressourcen-ID-Wert möglicherweise entweder in "appid" oder "azp" angezeigt. Das Token verfügt über einen der beiden Ansprüche, und Sie müssen in Ihrem Code entsprechend reagieren.

  • "tid": "Dies ist die Microsoft Entra-Mandanten-ID, die Sie der technischen Konfiguration Ihres Angebots im Microsoft Partner Center hinzufügen"

  • Sie können die oben übergebenen Felder überprüfen, um sicherzustellen, dass der Webhook-Aufruf gültig ist.

Wichtig

Microsoft erfordert, dass ISVs ihre Webhooks auf sichere Weise erstellen und Autorisierungsheader akzeptieren. Wenn Ihre aktuelle Webhook-Implementierung keine Autorisierungsheader akzeptieren kann, müssen Sie Ihre Webhooks aktualisieren und solche Endpunkte (unter Verwendung der oben genannten Richtlinien) schützen, um Unterbrechungen zu vermeiden.

Entwickeln und Testen

Zu Beginn des Entwicklungsprozesses wird empfohlen, API-Pseudoantworten auf Herausgeberseite zu erstellen. Diese Antworten können auf Beispielantworten in diesem Artikel basieren.

Wenn der Herausgeber für End-to-End-Tests bereit ist, sollten folgende Schritte ausgeführt werden:

  • Veröffentlichen Sie ein SaaS-Angebot für eine begrenzte Vorschauzielgruppe, und belassen Sie es in der Vorschauphase.
  • Legen Sie den Planpreis auf Null fest, um zu vermeiden, dass die tatsächlichen Abrechnungsausgaben beim Testen ausgelöst werden. Eine weitere Möglichkeit besteht darin, einen Nonzero-Preis festzulegen und alle Testkäufe innerhalb von 24 Stunden zu stornieren.
  • Stellen Sie sicher, dass alle Flows von Anfang bis Ende aufgerufen werden, um ein reales Kundenszenario zu simulieren.
  • Wenn der Partner den vollständigen Kauf- und Abrechnungsflow testen möchte, verwenden Sie hierfür ein Angebot mit einem höheren Preis als 0 €. Der Kauf wird in Rechnung gestellt, und eine Rechnung wird generiert.

Je nachdem, wo das Angebot veröffentlicht wird, kann ein Kaufablauf über das Azure-Portal oder Microsoft AppSource ausgelöst werden.

Die Aktionen Plan ändern, Menge ändern und Kündigen werden auf Herausgeberseite getestet. Auf Microsoft-Seite kann Kündigen sowohl über das Azure-Portal als auch das Admin Center (das Portal, in dem Microsoft AppSource-Käufe verwaltet werden) ausgelöst werden. Menge und Plan ändern kann nur im Admin Center ausgelöst werden.

Support

Supportoptionen für Herausgeber finden Sie unter Support für das Programm „Kommerzieller Marketplace“ im Partner Center.

Nächste Schritte