Configurar notificaciones de cambio que incluyan datos de recursos (notificaciones enriquecidas)

Microsoft Graph permite a las aplicaciones suscribirse y recibir notificaciones de cambios en los recursos que les interesan. Aunque puede suscribirse a notificaciones de cambios básicas, recursos como mensajes de chat de Microsoft Teams y recursos de presencia, por ejemplo, admiten notificaciones enriquecidas.

Las notificaciones enriquecidas incluyen los datos de recursos que han cambiado, lo que permite que la aplicación ejecute lógica de negocios sin tener que realizar una llamada API independiente para capturar el recurso modificado. Este artículo le guía por el proceso de configuración de notificaciones enriquecidas en la aplicación.

Recursos admitidos

Las notificaciones enriquecidas están disponibles para los siguientes recursos.

Nota:

Las notificaciones enriquecidas de las suscripciones a puntos de conexión marcadas con un asterisco (*) solo están disponibles en el punto de /beta conexión.

Recurso Rutas de acceso de recursos admitidas Limitaciones
evento de Outlook Cambios en todos los eventos del buzón de un usuario: /users/{id}/events Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
mensaje de Outlook Cambios en todos los mensajes del buzón de un usuario: /users/{id}/messages

Cambios en los mensajes de la bandeja de entrada de un usuario: /users/{id}/mailFolders/{id}/messages
Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
contacto personal de Outlook Cambios en todos los contactos personales del buzón de un usuario: /users/{id}/contacts

Cambios en todos los contactos personales de contactFolder de un usuario: /users/{id}/contactFolders/{id}/contacts
Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
Llamada de TeamsRegistro Todas las grabaciones de una organización: communications/onlineMeetings/getAllRecordings

Todas las grabaciones de una reunión específica: communications/onlineMeetings/{onlineMeetingId}/recordings

Una grabación de llamadas que está disponible en una reunión organizada por un usuario específico: users/{id}/onlineMeetings/getAllRecordings

Grabación de llamadas que está disponible en una reunión en la que se instala una aplicación de Teams determinada: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings *
Cuotas de suscripción permitidas:
  • Por aplicación y combinación de reuniones en línea: 1
  • Por aplicación y combinación de usuario: 1
  • Por usuario (para las suscripciones que realizan el seguimiento de las grabaciones en todas las aplicaciones en línea organizadas por el usuario): 10 suscripciones.
  • Por organización: 10 000 suscripciones totales.
  • Llamada de TeamsTranscript Todas las transcripciones de una organización: communications/onlineMeetings/getAllTranscripts

    Todas las transcripciones de una reunión específica: communications/onlineMeetings/{onlineMeetingId}/transcripts

    Transcripción de llamadas que está disponible en una reunión organizada por un usuario específico: users/{id}/onlineMeetings/getAllTranscripts

    Transcripción de llamadas que está disponible en una reunión en la que se instala una aplicación de Teams determinada: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts *
    Cuotas de suscripción permitidas:
  • Por aplicación y combinación de reuniones en línea: 1
  • Por aplicación y combinación de usuario: 1
  • Por usuario (para las transcripciones de seguimiento de suscripciones en todas las webMeetings organizadas por el usuario): 10 suscripciones.
  • Por organización: 10 000 suscripciones totales.
  • Canal de Teams Cambios en los canales de todos los equipos: /teams/getAllChannels

    Cambios en el canal en un equipo específico: /teams/{id}/channels
    -
    Chat de Teams Cambios en cualquier chat del inquilino: /chats

    Cambios en un chat específico: /chats/{id}
    -
    chatmessage de Teams Cambios en los mensajes de chat en todos los canales de todos los equipos: /teams/getAllMessages

    Cambios en los mensajes de chat en un canal específico: /teams/{id}/channels/{id}/messages

    Cambios en los mensajes de chat en todos los chats: /chats/getAllMessages

    Cambios en los mensajes de chat en un chat específico: /chats/{id}/messages

    Los cambios en los mensajes de chat de todos los chats de los que forma parte un usuario determinado son parte de: /users/{id}/chats/getAllMessages
    No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada.
    conversationMember de Teams Cambios en la pertenencia a un equipo específico: /teams/{id}/members



    Cambios en la pertenencia a un chat específico: /chats/{id}/members
    -
    Microsoft Teams onlineMeeting * Cambios en una reunión en línea: /communications/onlineMeetings(joinWebUrl='{encodedJoinWebUrl}')/meetingCallEvents * No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada. Una suscripción permitida por aplicación por reunión en línea. Para obtener más información, consulte Obtención de notificaciones de cambio para las actualizaciones de eventos de llamadas a reuniones de Microsoft Teams.
    Presencede Teams Cambios en la presencia de un solo usuario: /communications/presences/{id}

    Cambios en la presencia de varios usuarios: /communications/presences?$filter=id in ({id},{id}...)
    La suscripción para la presencia de varios usuarios está limitada a 650 usuarios distintos. No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada. Una suscripción permitida por aplicación por usuario delegado. Para obtener más información, consulte Obtención de notificaciones de cambios para las actualizaciones de presencia en Microsoft Teams.
    Equipo de Teams Cambios en cualquier equipo del inquilino: /teams

    Cambios en un equipo específico: /teams/{id}
    -

    Datos de recurso en la carga útil de notificación

    Las notificaciones enriquecidas incluyen los siguientes datos de recursos en la carga:

    • ID y tipo de la instancia de recurso modificada, devuelta en la propiedad resourceData.
    • Todos los valores de propiedad de esa instancia de recurso, cifrados como se especifica en la suscripción, se devuelven en la propiedadencryptedContent.
    • O, dependiendo del recurso, propiedades específicas devueltas en la propiedad resourceData. Para obtener sólo propiedades específicas, especifíquelas como parte de la URL del recurso en la suscripción, utilizando un $select parámetro.

    Crear una suscripción

    Las notificaciones enriquecidas se configuran de la misma manera que las notificaciones de cambio básicas, excepto que debe especificar las siguientes propiedades:

    • includeResourceData que debería establecerse en true para solicitar explícitamente datos de recursos.
    • encryptionCertificate , que contiene solo la clave pública que Usa Microsoft Graph para cifrar los datos de recursos que devuelve a la aplicación. Por motivos de seguridad, Microsoft Graph cifra los datos de recursos devueltos en una notificación enriquecida. Debe proporcionar una clave de cifrado pública como parte de la creación de la suscripción. Para obtener más información sobre cómo crear y administrar claves de cifrado, consulte Descifrado de datos de recursos a partir de notificaciones de cambios.
    • encryptionCertificateId el cual es su propio identificador para el certificado. Utilice este ID. para que coincida en cada notificación de cambios, qué certificado utilizar para el descifrado.

    También debe validar ambos puntos de conexión como se describe en Validación de puntos de conexión de notificación. Si decide usar la misma dirección URL para ambos puntos de conexión, recibirá y debe responder a dos solicitudes de validación.

    Ejemplo de solicitud de suscripción

    El siguiente ejemplo se suscribe a los mensajes de canal que se crean o actualizan en Microsoft Teams.

    POST https://graph.microsoft.com/v1.0/subscriptions
    Content-Type: application/json
    
    {
      "changeType": "created,updated",
      "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
      "resource": "/teams/{id}/channels/{id}/messages",
      "includeResourceData": true,
      "encryptionCertificate": "{base64encodedCertificate}",
      "encryptionCertificateId": "{customId}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secretClientState}"
    }
    

    Respuesta a la suscripción

    HTTP/1.1 201 Created
    Content-Type: application/json
    
    {
      "changeType": "created,updated",
      "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
      "resource": "/teams/{id}/channels/{id}/messages",
      "includeResourceData": true,
      "encryptionCertificateId": "{custom ID}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secret client state}"
    }
    

    Notificaciones del ciclo de vida de la suscripción

    Algunos eventos pueden interferir con el flujo de notificaciones de cambios en una suscripción existente. La suscripción a las notificaciones del ciclo de vida le informan de las acciones que debe realizar para mantener un flujo ininterrumpido. A diferencia de una notificación de cambio de recursos que informa de un cambio en una instancia de recurso, una notificación de ciclo de vida es sobre la propia suscripción y su estado actual en el ciclo de vida.

    Para obtener más información sobre cómo recibir y responder a las notificaciones de ciclo de vida, consulte Reducción de suscripciones que faltan y notificaciones de cambio.

    Validación de la autenticidad de las notificaciones

    Antes de ejecutar la lógica de negocios basada en los datos de recursos incluidos en las notificaciones de cambio, primero debe comprobar la autenticidad de cada notificación de cambio. De lo contrario, un tercero puede suplantar la aplicación con notificaciones de cambio falsas y hacer que ejecute su lógica de negocios incorrectamente, lo que puede provocar un incidente de seguridad.

    Para las notificaciones de cambio básicas que no contienen datos de recursos, solo tiene que validarlas en función del valor clientState , tal como se describe en Procesamiento de la notificación de cambio. Esta validación es aceptable, ya que puede realizar llamadas de Microsoft Graph de confianza posteriores para obtener acceso a los datos de recursos y, por tanto, el impacto de cualquier intento de suplantación de identidad es limitado.

    Para las notificaciones enriquecidas, realice una validación más exhaustiva antes de procesar los datos.

    En esta sección, explorará los siguientes conceptos de validación:

    Token de validación en la notificación de cambios

    Una notificación de cambio con datos de recursos contiene una propiedad adicional, validationTokens, que contiene una matriz de tokens web JSON (JWT) generados por Microsoft Graph. Microsoft Graph genera un solo token para cada par de aplicaciones e inquilinos distintos para los que hay un elemento en la matriz de valores . Tenga en cuenta que las notificaciones de cambio pueden contener una combinación de elementos para varias aplicaciones e inquilinos que se suscribieron con la misma notificationUrl.

    Nota:

    Microsoft Graph no envía tokens de validación para las notificaciones de cambio que se entregan a través de Azure Event Hubs porque el servicio de suscripción no necesita validar notificationUrl para Event Hubs.

    En el siguiente ejemplo, la notificación de cambios contiene dos elementos para la misma aplicación y para dos espacios empresariales diferentes, por lo que la matriz validationTokens contiene dos tokens que necesitan ser validados.

    {
        "value": [
            {
                "subscriptionId": "76619225-ff6b-4489-96ca-4ef547e78b22",
                "tenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee",
                "changeType": "created",
                ...
            },
            {
                "subscriptionId": "5cfe2387-163c-4006-81bb-1b5e1e060afe",
                "tenantId": "bbbbcccc-1111-dddd-2222-eeee3333ffff",
                "changeType": "created",
                ...
            }
        ],
        "validationTokens": [
            "eyJ0eXAiOiJKV1QiLCJhb...",
            "cGlkYWNyIjoiMiIsImlkc..."
        ]
    }
    

    El objeto de notificación de cambios se encuentra en la estructura del tipo de recurso changeNotificationCollection.

    Cómo validar

    Use la Biblioteca de autenticación de Microsoft (MSAL) para ayudarle a controlar la validación de tokens o una biblioteca de terceros para una plataforma diferente.

    Tenga en cuenta los siguientes principios:

    • Asegúrese de enviar siempre un código de estado HTTP 202 Accepted como parte de la respuesta a la notificación de cambios.
    • Responda antes de validar la notificación de cambio, incluso si se produce un error en la validación más adelante. Es decir, responda inmediatamente a la notificación de cambio, ya sea que almacene notificaciones en colas para su procesamiento posterior o que las procese sobre la marcha.
    • Aceptar una notificación de cambios evita reintentos de entrega innecesarios y también evita que cualquier posible agente deshonesto descubra si han superado o no la validación. Siempre puede optar por omitir una notificación de cambio no válida después de recibirla.

    En particular, realice la validación en todos los tokens JWT de la colección validationTokens. Si algún token falla, considere la notificación de cambios sospechosa e investigue más a fondo.

    Utilice los siguientes pasos para validar los tokens y las aplicaciones que generan tokens:

    1. Valide que el token no ha expirado.

    2. Valide que el Plataforma de identidad de Microsoft emitió el token y que el token no está alterado.

      • Obtenga las claves de firma desde el punto final de la configuración común:https://login.microsoftonline.com/common/.well-known/openid-configuration. La aplicación puede almacenar en caché esta configuración durante algún tiempo. La configuración se actualiza con frecuencia, ya que las claves de firma se giran diariamente.
      • Verifique la firma del token JWT usando esas teclas.

      No acepte tokens emitidos por ninguna otra autoridad.

    3. Valide que el token fue emitido para su aplicación que se está suscribiendo a notificaciones de cambio.

      Los siguientes pasos forman parte de la lógica de validación estándar en las bibliotecas de tokens de JWT y normalmente se pueden ejecutar como una llamada de función única.

      • Valida que el "público" del token coincida con el ID de tu aplicación.
      • Si tiene más de una aplicación recibiendo notificaciones de cambios, asegúrese de comprobar si hay varios ID.
    4. Crítico: Validar que la aplicación que generó el token representa al editor de notificación de cambios de Microsoft Graph.

      • Compruebe que la azp propiedad del token coincide con el valor esperado de 0bf30f3b-4a52-48df-9a82-234910c4a086.
      • Esta comprobación garantiza que una aplicación diferente que no sea Microsoft Graph no envió las notificaciones de cambio.

    Ejemplo de token JWT

    En el ejemplo siguiente se muestran las propiedades incluidas en el token JWT que son necesarias para la validación.

    {
      // aud is your app's id
      "aud": "925bff9f-f6e2-4a69-b858-f71ea2b9b6d0",
      "iss": "https://login.microsoftonline.com/9f4ebab6-520d-49c0-85cc-7b25c78d4a93/v2.0",
      "iat": 1624649764,
      "nbf": 1624649764,
      "exp": 1624736464,
      "aio": "E2ZgYGjnuFglnX7mtjJzwR5lYaWvAA==",
      // azp represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086
      "azp": "0bf30f3b-4a52-48df-9a82-234910c4a086",
      "azpacr": "2",
      "oid": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
      "rh": "0.AX0AtrpOnw1SwEmFzHslx41KkzsP8wtSSt9ImoIjSRDEoIZ9AAA.",
      "sub": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
      "tid": "9f4ebab6-520d-49c0-85cc-7b25c78d4a93",
      "uti": "mIB4QKCeZE6hK71XUHJ3AA",
      "ver": "2.0"
    }
    

    Ejemplo: comprobar los tokens de validación

    // add Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt nuget packages to your project
    public async Task<bool> ValidateToken(string token, string tenantId, IEnumerable<string> appIds)
    {
        var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
            new OpenIdConnectConfigurationRetriever());
        var openIdConfig = await configurationManager.GetConfigurationAsync();
        var handler = new JwtSecurityTokenHandler();
        try
        {
        handler.ValidateToken(token, new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            ValidIssuer = $"https://sts.windows.net/{tenantId}/",
            ValidAudiences = appIds,
            IssuerSigningKeys = openIdConfig.SigningKeys
        }, out _);
        return true;
        }
        catch (Exception ex)
        {
        Trace.TraceError($"{ex.Message}:{ex.StackTrace}");
        return false;
        }
    }
    

    Descifrar datos de recursos de notificaciones de cambios

    La propiedad resourceData de una notificación de cambios incluye sólo el ID. básico y la información de tipo de una instancia de recurso. La propiedad encryptedData contiene todos los datos de recursos, cifrados por Microsoft Graph utilizando la clave pública proporcionada en la suscripción. La propiedad también contiene los valores necesarios para la verificación y descifrado. Este cifrado se realiza para aumentar la seguridad de los datos del cliente a los que se accede a través de notificaciones de cambios. Es su responsabilidad proteger la clave privada para asegurarse de que un tercero no puede descifrar los datos del cliente, incluso si logra interceptar las notificaciones de cambio originales.

    En esta sección, aprenderá los siguientes conceptos:

    Administración de claves de cifrado

    1. Obtener un certificado con un par de claves asimétricas.

      • Puede usar un certificado autofirmado, ya que Microsoft Graph no comprueba el emisor del certificado y usa la clave pública solo para el cifrado.

      • Use Azure Key Vault para crear, rotar y administrar certificados de forma segura. Asegúrese de que las teclas cumplen los siguientes criterios:

        • La clave debe ser del tipo RSA.
        • El tamaño de la clave debe estar comprendido entre 2.048 bits y 4.096 bits.
    2. Exporte el certificado en formato X.509 codificado en Base64 e incluya solo la clave pública.

    3. Al crear una suscripción:

      • Proporcione el certificado en la propiedad encryptionCertificate mediante el contenido codificado en Base64 en el que se exportó el certificado.

      • Proporcione su propio identificador en la propiedad encryptionCertificateId.

        Este identificador le permite hacer coincidir sus certificados con las notificaciones de cambios que recibe y recuperar los certificados de su almacén de certificados. El identificador puede tener hasta 128 caracteres.

    4. Administre la clave privada de forma segura, de modo que el código de procesamiento de notificaciones de cambios pueda acceder a la clave privada para descifrar los datos de los recursos.

    Claves rotativas

    Para minimizar el riesgo de que una clave privada se vea comprometida, cambie periódicamente sus claves asimétricas. Siga estos pasos para introducir un nuevo par de claves:

    1. Obtener un nuevo certificado con un nuevo par de claves asimétricas. Utilícelo para todas las suscripciones nuevas que se estén creando.

    2. Actualice las suscripciones existentes con la nueva clave de certificado.

      • Haga que esta actualización forme parte de la renovación normal de la suscripción.
      • O bien, enumere todas las suscripciones y proporcione la clave. Utilice la operación revisión en la suscripción y actualice las propiedades encryptionCertificate y encryptionCertificateId.
    3. Tenga en cuenta los siguientes principios:

      • Durante algún tiempo, el certificado anterior podría seguir utilizándose para el cifrado. Su aplicación debe tener acceso tanto a los certificados antiguos como a los nuevos para poder descifrar el contenido.
      • Utilice la propiedad encryptionCertificateId en cada notificación de cambios para identificar la clave correcta a utilizar.
      • Descarte del certificado antiguo solo cuando no vea notificaciones de cambio recientes que hacen referencia a él.

    Descifrar los datos de recursos

    Para optimizar el rendimiento, Microsoft Graph utiliza un proceso de cifrado de dos pasos:

    • Genera una clave simétrica de un solo uso y la usa para cifrar los datos de recursos.
    • Utiliza la clave pública asimétrica (que usted proporcionó al suscribirse) para cifrar la clave simétrica y la incluye en cada notificación de cambios de esa suscripción.

    Suponga siempre que la clave simétrica es diferente para cada elemento de la notificación de cambios.

    Para descifrar los datos de recursos, su aplicación debería realizar los pasos invertidos, utilizando las propiedades bajo encryptedContent en cada notificación de cambios:

    1. Utilice la propiedad encryptionCertificateId para identificar el certificado a utilizar.

    2. Inicialice un componente criptográfico RSA con la clave privada. Una manera sencilla de inicializar un componente RSA es usar el método RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2) con una instancia X509Certificate2 , que contiene la clave privada descrita en Administración de claves de cifrado.

    3. Descifre la clave simétrica entregada en la propiedad dataKey de cada elemento de la notificación de cambios.

      Utilice Optimal Asymmetric Encryption Padding (OAEP) para el algoritmo de descifrado.

    4. Utilizar la clave simétrica para calcular la firma HMAC-SHA256 del valor en los datos.

      Compárelo con el valor en dataSignature. Si no coinciden, suponga que la carga está alterada y no la descifra.

    5. Use la clave simétrica con un estándar de cifrado avanzado (AES) (como los Aes de .NET) para descifrar el contenido de los datos.

      • Utilice los siguientes parámetros de descifrado para el algoritmo AES:

        • Relleno: PKCS7
        • Modo de cifrado: CSC
      • Ajuste el "vector de inicialización" copiando los primeros 16 bytes de la clave simétrica utilizada para el descifrado.

    6. El valor descifrado es una cadena JSON que representa la instancia del recurso en la notificación de cambios.

    Ejemplo: descifrar una notificación con datos de recursos cifrados

    En el ejemplo JSON siguiente se muestra una notificación de cambio que incluye valores de propiedad cifrados de una instancia de chatMessage en un mensaje de canal. El @odata.id valor especifica la instancia.

    {
      "value": [
        {
          "subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
          "changeType": "created",
          // Other properties typical in a resource change notification
          "resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
          "resourceData": {
            "id": "1565293727947",
            "@odata.type": "#Microsoft.Graph.ChatMessage",
            "@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
          },
          "encryptedContent": {
            "data": "{encrypted data that produces a full resource}",
            "dataSignature": "<HMAC-SHA256 hash>",
            "dataKey": "{encrypted symmetric key from Microsoft Graph}",
            "encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
            "encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
          }
        }
      ],
      "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
      ]
    }
    

    Para obtener una descripción completa de los datos enviados cuando se entregan las notificaciones de cambio, vea tipo de recurso changeNotificationCollection.

    Descifrar la clave simétrica

    Esta sección contiene algunos fragmentos de código útiles que utilizan C# y .NET para cada etapa de desencriptación.

    // Initialize with the private key that matches the encryptionCertificateId.
    X509Certificate2 certificate = <instance of X509Certificate2 matching the encryptionCertificateId property>;
    RSA rsa = certificate.GetRSAPrivateKey();
    byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);
    
    // Decrypt using OAEP padding.
    byte[] decryptedSymmetricKey = rsa.Decrypt(encryptedSymmetricKey, fOAEP: true);
    
    // Can now use decryptedSymmetricKey with the AES algorithm.
    

    Comparar la firma de datos utilizando HMAC-SHA256

    byte[] decryptedSymmetricKey = <the aes key decrypted in the previous step>;
    byte[] encryptedPayload = <the value from the data property, still encrypted>;
    byte[] expectedSignature = <the value from the dataSignature property>;
    byte[] actualSignature;
    
    using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
    {
        actualSignature = hmac.ComputeHash(encryptedPayload);
    }
    if (actualSignature.SequenceEqual(expectedSignature))
    {
        // Continue with decryption of the encryptedPayload.
    }
    else
    {
        // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
    }
    

    Descifrar el contenido de los datos del recurso

    Aes aesProvider = Aes.Create();
    aesProvider.Key = decryptedSymmetricKey;
    aesProvider.Padding = PaddingMode.PKCS7;
    aesProvider.Mode = CipherMode.CBC;
    
    // Obtain the initialization vector from the symmetric key itself.
    int vectorSize = 16;
    byte[] iv = new byte[vectorSize];
    Array.Copy(decryptedSymmetricKey, iv, vectorSize);
    aesProvider.IV = iv;
    
    byte[] encryptedPayload = Convert.FromBase64String(<value from data property>);
    
    string decryptedResourceData;
    // Decrypt the resource data content.
    using (var decryptor = aesProvider.CreateDecryptor())
    {
      using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
      {
          using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
          {
              using (StreamReader srDecrypt = new StreamReader(csDecrypt))
              {
                  decryptedResourceData = srDecrypt.ReadToEnd();
              }
          }
      }
    }
    
    // decryptedResourceData now contains a JSON string that represents the resource.