Implementación de un webhook en el servicio de SaaS

Al crear una oferta de SaaS comercializable en el Centro de partners, el partner proporciona la dirección URL del campoWebhook de conexión que se va a usar como punto de conexión HTTP. Microsoft llama a este webhook mediante la llamada HTTP POST para notificar al lado del editor los siguientes eventos que se producen en el lado de Microsoft:

Evento de webhook 1. Cuando se recibe 2. Si se acepta 3. Si se rechaza
ChangePlan Responder con HTTP 200 PATCH con éxito (este evento es opcional y autoaceptado en 10 segundos) PATCH con error O responder con 4xx (en un plazo de 10 segundos)
ChangeQuantity Responder con HTTP 200 PATCH con éxito (este evento es opcional y autoaceptado en 10 segundos) PATCH con error O responder con 4xx (en un plazo de 10 segundos)
Renew Responder con HTTP 200 No aplicable No aplicable
Suspend Responder con HTTP 200 No aplicable No aplicable
Unsubscribe Responder con HTTP 200 No aplicable No aplicable
Reinstate Responder con HTTP 200 No aplicable No aplicable (llamada a la API de eliminación para desencadenar la eliminación si no se puede aceptar la restablecimiento)

El editor debe implementar un webhook en el servicio de SaaS para mantener la coherencia del estado de la suscripción de SaaS con el del lado de Microsoft. El servicio de SaaS debe llamar a la API Get Operation para validar y autorizar la llamada de webhook y los datos de carga antes de realizar una acción basada en la notificación de webhook. El editor debe devolver HTTP 200 a Microsoft en cuanto se procese la llamada de webhook. Este valor confirma que el editor ha recibido correctamente la llamada de webhook.

Importante

El servicio de dirección URL de webhook debe estar en funcionamiento 24 x 7 y estar listo para recibir nuevas llamadas de Microsoft en todo momento. Microsoft tiene una directiva de reintento para la llamada de webhook (500 reintentos durante ocho horas), pero si el publicador no acepta la llamada y devuelve una respuesta, la operación de que el webhook notifica se producirá un error en el lado de Microsoft.

Importante

Los ISV deben evitar la deserialización estricta del esquema de Webhook. Microsoft se reserva el derecho de expandir el esquema en el futuro.

Importante

Los ISV deben validar el token de Microsoft Entra (token JWT) en su punto de conexión de webhook desde el encabezado de solicitud. Se trata de un token de portador estándar y proporcionará detalles del ISV sobre quién es el autor de la llamada. Obtenga más información sobre cómo validar el token en este artículo. learn.microsoft.com/azure/active-directory/develop/access-tokens

Ejemplo de carga de webhook de 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": 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
}

Ejemplo de carga de webhook de 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": 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
}

Ejemplo de carga de webhook de un evento de restablecimiento de suscripción:

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

Ejemplo de carga de webhook de un evento de renovación:

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

Ejemplo de carga de webhook de un evento de suspensión:


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

Ejemplo de carga de webhook de un evento de cancelación de suscripción:

Se trata de un evento de solo notificación. No hay ningún envío a ACK para este evento.


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

Protección de los webhooks

Debe proteger los webhooks para que ninguno de los puntos de conexión de Microsoft realice estas llamadas de webhook. Puede usar cualquier tecnología para implementar los webhooks, pero la implementación de webhook debe seguir las siguientes directrices de seguridad.

  • Microsoft llama a los webhooks con encabezados de autorización que contienen información necesaria para validar las llamadas. Debe habilitar los webhooks para poder recibir los encabezados de autorización. (No agregue detalles de autorización ni tokens de seguridad como tokens de SAS directamente en las direcciones URL del webhook. Estos webhooks podrían no recuperar los encabezados de autorización que Microsoft envía al llamar a los webhooks).

  • El token de portador JWT pasado en el encabezado authorization contiene los siguientes datos en la carga que puede usar para proteger los puntos de conexión.

  • "aud": "este es el identificador de aplicación de Microsoft Entra Identity que agrega a la configuración técnica de la oferta en el Centro de partners de Microsoft".

  • "appid" o "azp": este es el identificador de recurso que se usa al crear el token de autorización del publicador para llamar a las API de suministro de SaaS. Y en función de la configuración de la aplicación, puede ver este valor de identificador de recurso en "appid" o "azp". El token tiene cualquiera de las dos notificaciones y debe reaccionar en consecuencia en el código.

  • "tid": "este es el identificador de inquilino de Microsoft Entra que agrega a la configuración técnica de la oferta en el Centro de partners de Microsoft"

  • Puede comprobar los campos pasados anteriores para asegurarse de que la llamada de Webhook sea válida.

Importante

Microsoft comenzará a requerir que los ISV creen sus webhooks de forma segura y acepten encabezados de autorización. Si la implementación actual de Webhook no puede aceptar encabezados de autorización, debe actualizar los webhooks y proteger dichos puntos de conexión (con instrucciones anteriores) para evitar cualquier interrupción.

Desarrollo y pruebas

Para iniciar el proceso de desarrollo, se recomienda crear respuestas de API ficticias en el lado del editor. Pueden basarse en las respuestas de ejemplo que se proporcionan en este artículo.

Cuando el editor esté listo para las pruebas de un extremo a otro:

  • Publique una oferta de SaaS para un público preliminar limitado y manténgalo en la fase de versión preliminar.
  • Establezca el precio del plan en cero para evitar desencadenar gastos de facturación reales durante las pruebas. Otra opción es establecer un precio distinto de cero y cancelar todas las compras de pruebas en un plazo de 24 horas.
  • Asegúrese de que todos los flujos se invocan de un extremo a otro para simular un escenario de cliente real.
  • Si el partner quiere probar el flujo completo de compra y facturación, hágalo con una oferta cuyo precio sea superior a 0 USD. La compra se factura y se generará una factura.

Se puede desencadenar un flujo de compra desde Azure Portal o sitios de Microsoft AppSource, en función de dónde se publique la oferta.

Las acciones de cambiar plan, cambiar cantidad y cancelar suscripción se prueban desde el lado del editor. En el lado de Microsoft, se pueden desencadenar la acción de cancelar suscripción desde Azure Portal y el Centro de administración (el portal donde se administran las compras de Microsoft AppSource). Las acciones de cambiar cantidad y plan solo se pueden desencadenar desde el Centro de administración.

Obtención de soporte técnico

Consulte Soporte técnico para el programa Marketplace comercial en el Centro de partners para ver las opciones de soporte técnico para editores.

Pasos siguientes