Incorporer Microsoft Teams dans votre application

Cet article explique comment incorporer l’expérience Microsoft Teams dans votre application. Lorsque vous incorporez Teams dans votre application, vos utilisateurs peuvent lire et envoyer des messages Teams directement à partir de votre application, sans avoir à basculer entre votre application et Teams.

Pour améliorer le temps de réponse de votre application et réduire les coûts, vous devez réduire le nombre de fois où un message est lu à partir de Microsoft Graph. Cet article explique comment récupérer des messages une seule fois et les mettre en cache, puis utiliser les notifications de modification pour obtenir uniquement les messages suivants.

Étape 1 : Concevoir et configurer l’architecture

Le diagramme suivant montre l’architecture de haut niveau suggérée pour une application qui s’intègre à Teams.

Diagramme montrant l’intégration de Teams à une interface utilisateur d’application

L’architecture comprend trois composants :

  • Interface utilisateur de conversation qui obtient des entrées utilisateur et affiche des messages. L’interface utilisateur de conversation envoie des demandes d’API (telles que POST/GET des conversations, POST/GET des messages) aux API Teams. Il obtient également de nouveaux messages en temps réel à partir du composant serveur.

  • Composant serveur qui s’abonne aux notifications de modification en temps réel pour obtenir de nouveaux messages à partir des API Teams. Lorsque les API Teams envoient des notifications de modification, une URL de webhook est requise pour écouter les notifications de modification, et votre interface utilisateur, telle que le téléphone mobile des utilisateurs, peut ne pas avoir d’URL de webhook. Toutefois, le composant serveur a une URL de webhook stable. Les nouveaux messages sont ensuite envoyés à partir du composant serveur vers l’interface utilisateur de conversation, à l’aide de méthodes de communication telles que ASP.NET SignalR.

    Remarque

    Vous pouvez également choisir d’avoir le composant serveur, au lieu de l’interface utilisateur de conversation, d’effectuer toutes les demandes d’API aux API Teams et de mettre en cache tous les messages. Par exemple, si vous avez un autre composant système principal qui doit également effectuer des demandes d’API, par exemple pour la conformité et l’audit, vous pouvez choisir de centraliser les demandes d’API et la mise en cache sur le composant serveur à la place.

  • Cache qui conserve les messages. Pour améliorer le temps de réponse de votre application et réduire potentiellement les coûts pour vous, réduisez la lecture du même message plusieurs fois en stockant les messages dans ce cache. Vous ne souhaitez pas être surpris par les frais de consommation d’API ultérieurement. Pour savoir comment configurer un cache, consultez Ajouter une mise en cache pour améliorer les performances dans Azure Gestion des API.

    Certaines API Teams ont des exigences de licence et de paiement. Pour plus d’informations, consultez Modèles de paiement et conditions de licence pour plus d’informations.

Après avoir configuré ces composants, vous pouvez commencer à utiliser les API Teams.

Étape 2 : Créer une conversation

Avant d’envoyer un nouveau chatMessage, vous devez créer une conversation en affectant des membres. L’exemple suivant montre comment créer une conversation de groupe. Pour obtenir d’autres exemples qui montrent comment créer différents types de conversation, consultez Créer une conversation.

Demande

POST https://graph.microsoft.com/v1.0/chats
Content-Type: application/json

{
    "chatType": "group",
    "members": [
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "roles": [
                "owner"
            ],
            "user@odata.bind": "https://graph.microsoft.com/v1.0/users('adams@contoso.com')"
        },
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "roles": [
                "owner"
            ],
            "user@odata.bind": "https://graph.microsoft.com/v1.0/users('gradyA@contoso.com')"
        },
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "roles": [
                "owner"
            ],
            "user@odata.bind": "https://graph.microsoft.com/v1.0/users('4562bcc8-c436-4f95-b7c0-4f8ce89dca5e')"
        }
    ]
}

Réponse

HTTP/1.1 201 Created
Content-Type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity",
    "id": "19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2",
    "topic": null,
    "createdDateTime": "2023-01-11T01:34:18.929Z",
    "lastUpdatedDateTime": "2023-01-11T01:34:18.929Z",
    "chatType": "group",
    "webUrl": "https://teams.microsoft.com/l/chat/19%3Ab1234aaa12345a123aa12aa12aaaa1a9%40thread.v2/0?tenantId=4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1",
    "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1",
    "viewpoint": null,
    "onlineMeetingInfo": null
}

Étape 3 : Envoyer un message dans la conversation

Les membres de la conversation peuvent s’envoyer des messages les uns aux autres. L’exemple suivant montre comment envoyer un message simple. Pour obtenir d’autres exemples, notamment l’envoi d’autres médias tels que des pièces jointes et des cartes adaptatives, consultez Envoyer chatMessage.

Demande

POST https://graph.microsoft.com/v1.0/chats/19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2/messages
Content-type: application/json

{
  "body": {
    "content": "Hello World"
  }
}

Réponse

HTTP/1.1 201 Created
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats('19:b1234aaa12345a123aa12aa12aaaa1a9%40thread.v2')/messages/$entity",
    "id": "1673482643198",
    "replyToId": null,
    "etag": "1673482643198",
    "messageType": "message",
    "createdDateTime": "2023-01-12T00:17:23.198Z",
    "lastModifiedDateTime": "2023-01-12T00:17:23.198Z",
    "lastEditedDateTime": null,
    "deletedDateTime": null,
    "subject": null,
    "summary": null,
    "chatId": "19:b1234aaa12345a123aa12aa12aaaa1a@thread.v2",
    "importance": "normal",
    "locale": "en-us",
    "webUrl": null,
    "channelIdentity": null,
    "policyViolation": null,
    "eventDetail": null,
    "from": {
        "application": null,
        "device": null,
        "user": {
            "id": "87d349ed-44d7-43e1-9a83-5f2406dee5bd",
            "displayName": "John Smith",
            "userIdentityType": "aadUser"
        }
    },
    "body": {
        "contentType": "text",
        "content": "Hello world"
    },
    "attachments": [],
    "mentions": [],
    "reactions": []
}

Étape 4 : Récupérer les messages

Utilisez la GET méthode HTTP sur la ressource chatMessages pour récupérer des messages.

Pour améliorer le temps de réponse de votre application, réduire la limitation et réduire potentiellement les coûts pour vous, réduisez au minimum la lecture du même message plusieurs fois. Utilisez la GET méthode HTTP comme exportation à usage unique, ou lorsque les notifications de modification ont expiré et que vous souhaitez synchroniser à nouveau les messages. Sinon, reposez-vous sur vos notifications de cache et de modification.

Microsoft Graph offre plusieurs façons de récupérer des messages de conversation :

À l’aide de /getAllMessages, vous pouvez obtenir des messages sur toutes les conversations d’un utilisateur. Cette API est conçue pour les applications principales, telles que les applications d’audit et de conformité, qui reçoivent souvent des messages sur toutes les conversations à la fois. Il prend uniquement en charge les autorisations d’application . En outre, il s’agit d’une API limitée.

À l’aide de /messages, vous pouvez effectuer des appels d’API à partir de l’interface utilisateur à l’aide d’autorisations déléguées , comme décrit à l’étape 1.

Différentes API ont des limites de limitation différentes. Par exemple, l’API par conversation /messages a une limite de 30 demandes par seconde (rps) par application et par locataire. Si un locataire a 50 utilisateurs et que chaque utilisateur a 15 conversations en moyenne et que vous souhaitez récupérer des messages pour tous les utilisateurs et toutes les conversations au début de votre système, vous avez besoin d’au moins 50 utilisateurs x 15 demandes de conversation/utilisateur = 750 requêtes. Dans ce cas, il est préférable de répartir les requêtes sur au moins 750 requêtes / 30 rps = 25 secondes. Étant donné qu’il existe une limite (maximale $top=50) au nombre de messages retournés dans une réponse, vous devrez peut-être effectuer plusieurs demandes pour obtenir tous les messages.

L’exemple suivant montre comment utiliser l’API par conversation /messages . Par défaut, la liste des messages retournée est triée par lastModifiedDateTime. Cet exemple trie en fonction de createdDateTime. Le tri est spécifié via le paramètre de orderBy requête dans la requête.

Les applications de messagerie interactive classiques affichent uniquement les messages les plus récents par défaut, et les utilisateurs peuvent ensuite charger des messages plus anciens en paginant, en faisant défiler ou en cliquant. Pour récupérer uniquement les messages dont vous avez besoin, les deux API ci-dessus prennent également en charge le filtrage (par exemple, $top=10, $filter=lastModifiedDateTime gt 2019-03-17T07:13:28.000z).

Demande

GET https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2/messages?$top=2&$filter=lastModifiedDateTime gt 2021-03-17T07:13:28.000z&$orderby=createdDateTime desc

Réponse

HTTP/1.1 200 OK
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('87d349ed-44d7-43e1-9a83-5f2406dee5bd')/chats('19%3Ab1234aaa12345a123aa12aa12aaaa1a9%40thread.v2')/messages",
    "@odata.count": 2,
    "@odata.nextLink": "https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2/messages?$top=2&$filter=lastModifiedDateTime+gt+2021-03-17T07%3a13%3a28.000z&$orderby=createdDateTime+desc&$skiptoken=A111wwAwAA1ww1AwA1wwA1Aww111AA1wAwAAwAAwAAAwA1w1AAAwAAwww1Aww1AwAAwwAAA1AA1wAwAAw111wA11AAAww11Aw1wwww1wAwwwAAwwAwAwAAw1",
    "value": [
        {
            "id": "1673543687527",
            "replyToId": null,
            "etag": "1673543687527",
            "messageType": "message",
            "createdDateTime": "2023-01-12T17:14:47.527Z",
            "lastModifiedDateTime": "2023-01-12T17:14:47.527Z",
            "lastEditedDateTime": null,
            "deletedDateTime": null,
            "subject": null,
            "summary": null,
            "chatId": "19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2",
            "importance": "normal",
            "locale": "en-us",
            "webUrl": null,
            "channelIdentity": null,
            "policyViolation": null,
            "eventDetail": null,
            "from": {
                "application": null,
                "device": null,
                "user": {
                    "id": "6789f158-72b1-4a63-9959-1f006381132b",
                    "displayName": "Adele Vance",
                    "userIdentityType": "aadUser",
                    "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1"
                }
            },
            "body": {
                "contentType": "html",
                "content": "<p>Good morning, world!</p>"
            },
            "attachments": [],
            "mentions": [],
            "reactions": []
        },
        {
            "id": "1673482643198",
            "replyToId": null,
            "etag": "1673482643198",
            "messageType": "message",
            "createdDateTime": "2023-01-12T00:17:23.198Z",
            "lastModifiedDateTime": "2023-01-12T00:17:23.198Z",
            "lastEditedDateTime": null,
            "deletedDateTime": null,
            "subject": null,
            "summary": null,
            "chatId": "19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2",
            "importance": "normal",
            "locale": "en-us",
            "webUrl": null,
            "channelIdentity": null,
            "policyViolation": null,
            "eventDetail": null,
            "from": {
                "application": null,
                "device": null,
                "user": {
                    "id": "87d349ed-44d7-43e1-9a83-5f2406dee5bd",
                    "displayName": "John Smith",
                    "userIdentityType": "aadUser",
                    "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1"
                }
            },
            "body": {
                "contentType": "text",
                "content": "Hello world"
            },
            "attachments": [],
            "mentions": [],
            "reactions": []
        }
    ]
}

Dans cet exemple, le contentType peut être text ou html; assurez-vous que votre application peut afficher les deux.

Pour obtenir des images incorporées dans le message de conversation, effectuez un deuxième appel pour récupérer chatMessageHostedContent. Pour plus d’informations, consultez Obtenir chatMessageHostedContent.

Nous vous recommandons que votre application surveille le champ chatMessage.policyViolation.dlpAction, surveille les notifications de modification dans ce champ et masque ou signale les messages conformément à la protection contre la perte de données (DLP) ou à des règles similaires définies par votre organization. Les valeurs valides sont None, NotifySenderet BlockAccess. Actuellement, Teams ignore BlockAccessExternal. Pour plus d’informations sur ces valeurs, consultez type de ressource chatMessagePolicyViolation.

Certains messages sont des messages système. Par exemple, le message système suivant indique qu’un nouveau membre a rejoint la conversation.

{
  "id": "1616883610266",
  "replyToId": null,
  "etag": "1616883610266",
  "messageType": "systemEventMessage",
  "createdDateTime": "2021-03-28T03:50:10.266Z",
  "lastModifiedDateTime": "2021-03-28T03:50:10.266Z",
  "lastEditedDateTime": null,
  "deletedDateTime": null,
  "subject": null,
  "summary": null,
  "chatId": null,
  "importance": "normal",
  "locale": "en-us",
  "webUrl": "https://teams.microsoft.com/l/message/19%3A4a95f7d8db4c4e7fae857bcebe0623e6%40thread.tacv2/1616883610266?groupId=fbe2bf47-16c8-47cf-b4a5-4b9b187c508b&tenantId=2432b57b-0abd-43db-aa7b-16eadd115d34&createdTime=1616883610266&parentMessageId=1616883610266",
  "policyViolation": null,
  "from": null,
  "body": {
    "contentType": "html",
    "content": "<systemEventMessage/>"
  },
  "channelIdentity": {
    "teamId": "fbe2bf47-16c8-47cf-b4a5-4b9b187c508b",
    "channelId": "19:4a95f7d8db4c4e7fae857bcebe0623e6@thread.tacv2"
  },
  "onBehalfOf": null,
  "attachments": [],
  "mentions": [],
  "reactions": [],
  "eventDetail": {
    "@odata.type": "#microsoft.graph.membersAddedEventMessageDetail",
    "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
    "members": [{
        "id": "06a5b888-ad96-455e-88ef-c059ec4e4cf0",
        "displayName": null,
        "userIdentityType": "aadUser"
      },
      {
        "id": "1fb8890f-423e-4154-8fbf-db6809bc8756",
        "displayName": null,
        "userIdentityType": "aadUser"
      }
    ],
    "initiator": {
      "application": null,
      "device": null,
      "user": {
        "id": "9ee3dc1b-6a70-4582-8bc5-5dd35336b6c3",
        "displayName": null,
        "userIdentityType": "aadUser"
      }
    }
  }
}

Étape 5 : Mettre en cache les messages

Étant donné que chaque message que vous recevez à partir de getAllMessages ou d’une notification de modification est soumis à des frais de consommation, vous souhaiterez réduire la lecture du même message plusieurs fois. Nous vous recommandons de mettre en cache les messages pendant au moins quelques heures afin qu’un utilisateur puisse rapidement rouvrir une conversation récente. Ne mettez pas en cache les messages plus longtemps que ce qui est autorisé par les stratégies de rétention de votre organization.

À l’étape 6, vous allez décider si le cache est par utilisateur ou non.

Étape 6 : S’abonner aux notifications de modification

Microsoft Graph propose plusieurs types de notifications de modification pour les messages, comme spécifié par les propriétés de ressource correspondantes :

  • Par conversation : "resource": "/chats/{id}/messages"
  • Par utilisateur, sur toutes les conversations : "resource": "/users/{id}/chats/getAllMessages"
  • Par locataire, sur toutes les conversations : "resource": "/chats/getAllMessages"
  • Par application, sur toutes les conversations d’un locataire où l’application est installée : "resource": "/appCatalogs/teamsApps/{id}/installedToChats/getAllMessages"

Si vous souhaitez suivre uniquement des conversations spécifiques, /messages est une option, mais vous devez prendre en compte le nombre de conversations différentes que vous devez suivre. Il existe une limite (par exemple, 10 000) sur le nombre de notifications de modification par conversation ; Pour plus d’informations, consultez abonnements. Au lieu de cela, envisagez de vous abonner à l’une des trois /getAllMessages options, qui permettent d’obtenir des messages sur toutes les conversations d’un utilisateur, d’un locataire ou d’une application.

Les quatre options sont appelées par votre composant de serveur principal. Étant donné qu’ils prennent tous en charge les autorisations d’application , faites attention à la logique de contrôle d’accès pour afficher et masquer les conversations en conséquence lorsque les utilisateurs rejoignent ou quittent. L’option par utilisateur, qui prend également en charge les autorisations déléguées , peut être plus facile à implémenter, car les notifications de modification sont déjà spécifiques à l’utilisateur ; Toutefois, cela peut être plus coûteux à long terme, car le même message déclencherait plusieurs notifications de modification, une pour chaque utilisateur abonné, et vous aurez peut-être besoin d’un cache plus grand pour stocker les messages dupliqués. Pour plus d’informations sur les autorisations et les conditions de licence pour les différentes ressources abonnées, consultez Créer un abonnement.

Les abonnements aux notifications de modification ont des frais de consommation. Spécifiez le model paramètre sur la propriété de ressource , comme indiqué dans l’exemple suivant.

Lors de la création de l’abonnement, assurez-vous que la propriété includeResourceData a la valeur trueet que vous avez spécifié les propriétés encryptionCertificate et encryptionCertificateId . Sinon, le contenu chiffré n’est pas retourné dans les notifications de modification. Pour plus d’informations, consultez Configurer les notifications de modification qui incluent des données de ressources.

L’exemple suivant montre comment obtenir tous les messages par utilisateur. Avant d’utiliser cet exemple, le point de terminaison de notification d’abonnement (spécifié dans la propriété notificationUrl ) doit être en mesure de répondre à une demande de validation, comme décrit dans Configurer des notifications pour les modifications apportées aux données utilisateur. Si la validation échoue, la demande de création de l’abonnement retourne une 400 Bad Request erreur.

Pour plus d’informations sur cet exemple, consultez Créer un abonnement.

Demande

POST https://graph.microsoft.com/v1.0/subscriptions
Content-type: application/json

{
  "changeType": "created,updated,deleted",
  "notificationUrl": "https://webhook.azurewebsites.net/api/send/myNotifyClient",
  "resource": "/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/getAllMessages?model=B",
  "expirationDateTime": "2023-01-10T18:56:49.112603+00:00",
  "clientState": "ClientSecret",
  "includeResourceData": true,
  "encryptionCertificate": "MMMM/sMMMsssMsMMMsMMsMMMs4sMMsM4ssMsMsMMMss4ssMMMssss...s4sMMMMsM444ssM4MMsssMMMMsM4MMM4sMsM4MMsM44MMM4ssss4Ms4sMM4MMMMM4MMs+ss4MsMssMss4s==",
  "encryptionCertificateId": "44M4444M4444M4M44MM4444MM4444MMMM44MM4M4"
}

Réponse

HTTP/1.1 201 Created
Content-type: application/json

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#subscriptions/$entity",
  "id": "88aa8a88-88a8-88a8-8888-88a8aa88a88a",
  "resource": "/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/getAllMessages?model=B",
  "applicationId": "aa8aaaa8-8aa8-88a8-888a-aaaa8a8aa88a",
  "changeType": "created,updated,deleted",
  "clientState": "ClientSecret",
  "notificationUrl": "https://webhook.azurewebsites.net/api/send/myNotifyClient",
  "notificationQueryOptions": null,
  "lifecycleNotificationUrl": null,
  "expirationDateTime": "2023-01-10T18:56:49.112603Z",
  "creatorId": "8888a8a8-8a88-888a-88aa-8a888a88888a",
  "includeResourceData": true,
  "latestSupportedTlsVersion": "v1_2",
  "encryptionCertificate": "MMMM/sMMMsssMsMMMsMMsMMMs4sMMsM4ssMsMsMMMss4ssMMMssssssM4s4MMMsMMMMMMMMsMMMMMMMssMMsMMMMMMMMM4MMMMMsMMMMMMMssMMsMMMMMMMMMM4MMMssMsMMMMMMMs4MMMMsMM4sssMsM4MsMMMsMssMMsMsMMM4MMssMMMsMssMMsMsMMMsMMssMMMsMsMsMMssMsMMMMMMMsM4MMMss4ssMMMsMMssM4MsMsM4Ms4sM4MssMssMsMssMMMMMMsMMMMMsMMsssMMMMMMMMMssMMMMMMMMsMssMMMMM4ssMMs4sMsM/+MM4444s4M/+4sss4MMMMMsMsMsss/s/sMMsMss4sMsMMMss4M4Ms44M4M4MsssssM4M4MMMM444Mss4+s4M44MsssMMMs4Ms4MsMMsMMsMsMMM4sMMMMsssMssssMMss44MMs+MMssMsMsM4sMMs4MsMsM4ssM4MMMsMMs4sMMM4MsM+MsMss+sMsMM4sMM4sMMM4ss4ssssMMMsssM4MMssM+MsM/sMMss4MsMMM44+/MMMsMs4s44M++ssssssMMs/MsMMMMsMMssMsssssMMss4MMMsM4s4MssMsMssMsMMMMMMs4sMMssMsMMMM/ss4sMMsMMsMMMsMMMMMsssM4MMsMMMsMMMMMsssMMsMsMMssMsMMMsMMMMMMMsMsMsMMMsMMMMMMMsMsMMMMMsMMMMMMMsMMMMMsMsMsMsMMMMMMMsMMssMsMMMMsMsM4Ms+sMssMs4sMsMsssM4M4Ms4MMMMMMMMMssssMMMsssMsMMMMsMMMMMMs4sssM4MMMMMMsMMMMMMsMMsssssMMsMs4sM4MsMs4sM4Mss44ssM4ss44ssMsssM4sssMsM4MssMMsM44sMMsMMM4MM4MsMM4MMMMsM4MMM4MMMMMsMMssMsMsMMMsM4MsMsMsMM4sssMsMsMMMsMMMMMMMMMMM4s4sMM4Ms4sssssMsMsMM4sMsssMMssM4MMMMMMMMsMMMMMMMMsMM4MMssMMM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4M4MMMMMMsMMMMMMsMMsssssMMsMs4sM4MsMs4ssMMsM4MsM4MsMM4MMsMMM4sMMsMMMMMsMsMMMM4MMsssMM4MMMMsMM4sssMsMsMMMMMsMMM4MsMssMMMMsssMsMMMMssMsMMsMM4sMssM4MssMMsMM4sMssssM4ssMMsM44sMMsMMM4MM4MsMM4MMMMsM4MMM4MMMMMsMMssMsMsMMMsM4MsMsMsMM4sssMsMsMMMsMMMMMMMMMMM4s4sMM4Ms4ssss4MsMsMM4sMsssMMssM4MMMMMMMMsMMMMMMMMsMM4MMssMMM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4M4MM4MM4MsMsMMMMMsM4M4ssMMMssssMMMMMsM/s4MsMMMMsMMMM4MMs4MMMMMMsMsMsMMMM4MMMMsMsMssMMssMMsssMssM4ss4MssM4ssMMssssssMMsss4ss44sssMsMsMMMM4MssMsMMMMMMMMMMMsssMMsMMMMMM/sMM4sMssM4MssM4ssMMss4MsMsMsM44sM4MssMssMsMsM4MMMM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4MsssMssMMsMs4sM4MsMM4ssMMsM4MsM4MssM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4MsssMssMMsMs4sM4MsMs4ssMMsM4MsM4MssM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4MsssMssMMsMs4sM4MsMs4ssMMsM4MsM4MssM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4MsssMssMMsMs4sM4MsMM4ssMMsM4MsM4MssM4MMMMsMsMMssMsMMMsMMMMMMMsMMMsM4MsMM4MM4MsMsMMMMsMMMsMMMMssMssss4s+MMM44MMMsMsMM4MM4MsMMMMMMMMMMsMMMMMMsMMMsssMsMMMMsMMsMMMssssssM4s4MMMsMMMMMMMMMMMM4MMMMssss444MsMsMMM44MM/444sMMMs4sMsMM4sMMMssMM4+M4sssMs+MsMMMMM/M/s4MMssM4ssss/4MMMsssMsMMss44sMsss4++ss/4s+s4sMs+4sM4MsM/4/MssMMMsMssMs4MsMss4MMsMsMssssssMMM4MsMM4s+MMM4M4sMMMMs4s4sMMMMsM444ssM4MMsssMMMMsM4MsMsMMM4sMsMs4sMsMMMMMs4MsMsMsMsM4sMs4sMMMMMsssMssMsMsMMss4MMM4sMsM4sMMssMMsM44MM4ss4s4Ms44sMMM4ssss4Ms4sMM4MMMMM4MMs+ss4MsMssMss4s==",
  "encryptionCertificateId": "44M4444M4444M4M44MM4444MM4444MMMM44MM4M4",
  "notificationUrlAppId": null
}

Étape 7 : Recevoir et déchiffrer les notifications de modification

Chaque fois qu’une modification est apportée à la ressource abonnée, une notification de modification est envoyée à notificationUrl. Pour des raisons de sécurité, le contenu est chiffré. Pour déchiffrer le contenu, consultez Déchiffrement des données de ressources à partir de notifications de modification.

Lorsque vous créez l’abonnement, assurez-vous que la propriété includeResourceData a la valeur trueet que vous avez spécifié les propriétés encryptionCertificate et encryptionCertificateId . Sinon, le contenu chiffré n’est pas retourné dans les notifications de modification. Pour en savoir plus, consultez la section Validation du point de terminaison de notification de la page Configuration des notifications pour les modifications des données utilisateur.

Requête (envoyée par Microsoft Graph)

POST https://webhook.azurewebsites.net/api/send/myNotifyClient
Content-type: application/json

{
  "value": [
    {
      "subscriptionId": "88aa8a88-88a8-88a8-8888-88a8aa88a88a",
      "changeType": "created",
      "clientState": "ClientSecret",
      "subscriptionExpirationDateTime": "2023-01-10T11:03:37.0068432-08:00",
      "resource": "chats('19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2')/messages('1677774058888')",
      "resourceData": {
        "id": "1677774058888",
        "@odata.type": "#Microsoft.Graph.chatMessage",
        "@odata.id": "chats('19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2')/messages('1677774058888')"
      },
      "encryptedContent": {
        "data": "sMMsMsMM+3MMMs8MMsMMsss5M+M0+8sMMsM96MM/8MMMsM4MM12sMsssMMsssMsMMssMs8Mss6sssMMsM2ssssssssMss7sssMs4s35ssMs+0ss1sssMsMssMMMMsssss5MsMssssss+sMsMMMM8s4M3MMsMssM54s1ssssMs4ssMsss3MMM8M4sM+3MMss7MM8sMsMMMs3sssss5MssMss6s+Ms7sMssMMssMsMMss1sMs2sM6sss6sMssssMss7s1MMs7/Msssss5M9M7sMsMMMsMs+MMs+MsMMsMsMMMMsMMMss1M2ssMM8M3sMMMsMss2MMMMsM+ss0M+sssMM4M+sMsM69sMs+sMsssM+MMsMsMM/ssssMMMMMss/s6/47Ms0s5Ms6MsssM2sss4MMMMMMsMsMMM+s8MssMsMMssMMs+MMMM56ss0sMM+sssMsss1ssMsMs21s3MssM9ssMsss9M2+MM3sMMMMMM7MM770MMM2MMsssM11MsssMssMsMsMM2sM1s+84MMs6sss8MsMMsMMsMM3MMssMss1MssMsMsMMMMsMsssMMsssM1sssssM9MMM6s4MMss524sMMMssMs4ss3/+ssssss8MMs2ssMMs2MsMsMMssM8MMMMsMM0sss4MMs/sMsMMs0sMMsssss135sssss9+sssMsMMsMsssMsMsMsMsMM7Ms+MssMsMM1sMssss5s64sMss6sMs6sM0MMs3s29MMssM62ssMsMMssMsM0ssssssss+sM1MsM3sMM9sssssMMMsssMMsMsMsssMssssssMsMssMMMsM8Ms5MsssMM9ss/4MssMs3s5M81sMMssssMMMssMMs7Ms2M9M+7MsssMss6sM0sssM7M0ssMssssMMsMMs9s4MsMsMM6MMsMMssMMssM+Mss6MM8MMM6MM1s75MsssMMsM+MMMs2s9M1MMMsMMs1MssMsssssssMs8MsMsMMMMMM7sMsss0MssMsMMssMMM/sM0M01s2M7MsssssMs37MMs140sMMM0ssMMM/ssMMs3sMsM+Ms+sMMsM3MMssMssMsssss6MMssMMMs1MMMMMsssMs0sM9sMMMss+sssss2sssMssMsMMM1MssMMMs8MMMssssMM99ssMsssMssss2Ms5sMs1/5MMssssMsMM3MMMMM1MsMsMsMMsMMssMsMMsssssMs9Mss6Mss+sM+73Msss0ssMsss8sMssMssssssssssMM9MMMMsMMMMMMMM5MMMM27sM+ssMG",
        "dataSignature": "sMM+sss2sMssMMsMMMMMMMM6ssMs93MssMMM8sMMMMM=",
        "dataKey": "MMsMMMMMss7sssM34sMMsMMsMssMss7MssMssss+MM+4sMsssMss6Msss9sMssMssMsssMM0+MMsss0sMs8MMsMssss2MMMMsMsssMMsMsM3MssMs9ss5sssMMsMssMsMMM6MMssMsM1M+MMMMsMss3MMsssMs9s0ssMs/1sM6ssMMssM+Ms9MsssMMM8MMssssMMs2s94MsMssMMM92/MMMs4Ms8/ssssssMs5+0s+Ms2M7sMMMMsMMsMsMs+5MMM3sssMsMMMsM8sMss+MssssMsMs/MMsMM5ssssM8M0s0MM06sssMMsMM4MsssMMsMssMM9M9MsMMMMM7sMsMM==",
        "encryptionCertificateId": "44M4444M4444M4M44MM4444MM4444MMMM44MM4M4",
        "encryptionCertificateThumbprint": "07M3411M4904M3M78MM8211MM4589MMMM47MM7M6"
      },
      "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1"
    }
  ],
  "validationTokens": [
    "ssM0sMMsMsMMM1MsMMMssMssMsMMMsM1MsMsMss1sMM6Ms1MMMMMMM5MMsssMs9ssM1sMs9MsMMMMsssssMsMsssMMM6Ms1MMMMMMM5MMsssMs9ssM1sMs9MsMMMMsssssM9.ssMssMMsMsMsMsssMMMsMs04M2M2MMM1MMMsMMs4MM1sM2MsMMM5MsM3MsMsMMMss3MsMsMssMMsssssM3M0ss53sM5ss3ssMs5ssM8sMMMsMsM3Ms0sMMMsMMMsMMMsMMM3Ms0sMsMsMMMsMMMsMsMsMssssMM0MsssMsssMsssMsMsMMMsMsMsMsM2MsMsMsM3MMMsMsM4sMM6MMM3MsM2MMM1MMssMMssMsssMMMsM1sMssM5MMs4ssMsMMssMMMMM2MsMMMsMMsMMM0sMMMssMMsMMM6MsMsMsMsMsMsMMMsMMMsMMssMs05MMssMMMsMMssMMM0MMM4MsMsMsMssMssMMMsMsssMsMsMssssMM6Mss0sMMsMs8ss3MsMsssssMss3MsssM0MsM0MsMsMMssMMMsMsMsMMMsMs1sMMssMMM2MMMsMMMsMMMsMM8sMMMssMMsMsMsMsMsMsMsMM1sMsssMMMsMMMsMsssMM1sMMMsMMM0M2M2MMssMMMssMM6MsMsMMMsMMM3MMsMMMMMMsMMsMM4MsMsMsMsMssMsMMsMMMsM05MsMssMMssMMM5sMsMMMMMMsMsMsM1MsM6MsMsMsMsMsM1MMM3MMMsMMM5Ms1sM2M1MMMsMMM0MMMsMsM1MsMsMsMsMMM6MsM0MsMsMMssMMMsMsMsMMMsMs1sMMssMMM2MMMsMMMsMMMsMMMsMsM0sMM6MssMMs1sMMMssMsMMMMMssMMss16MMMsMMM2MMMsMsMsMsMssM.s16ssMMM97sM_MMs_ss8s8s3MMs95ssMMM8M6ss4M4Ms3sMMMs-M_7ss80MMMsss6ssM0sMM20MsMMs15sMM_ssMsssMMMs9ssM0M_sss5sMssMsMss4s-M-8Ms1ssM8sMsMMss9sMsMsMMMMMMMsMs6MMss2MMMsMMss0MMssMMssMssMMMMMMMMsMs817ssssssMss8MMMssMMMMsss0sMs1ssM0sM1ssMMMs6MMMMss6ss_sMMss3M4MM3sMss45s4s8MMss6s75ssMsM5sssMM0MMMMMM_1ssMMMMsMMMssMs44sMs4MssM5s-__ss5MMs6sMM_MMss5MsMMMM"
  ]
}

Contenu déchiffré

{
  "@odata.context": "https://graph.microsoft.com/$metadata#chats('19%3Ab1234aaa12345a123aa12aa12aaaa1a9%40thread.v2')/messages/$entity",
  "id": "1677774058888",
  "replyToId": null,
  "etag": "1677774058888",
  "messageType": "message",
  "createdDateTime": "2023-01-10T18:07:30.302Z",
  "lastModifiedDateTime": "2023-01-10T18:07:30.302Z",
  "lastEditedDateTime": null,
  "deletedDateTime": null,
  "subject": "",
  "summary": null,
  "chatId": "19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2",
  "importance": "normal",
  "locale": "en-us",
  "webUrl": null,
  "from": {
    "application": null,
    "device": null,
    "user": {
      "userIdentityType": "aadUser",
      "id": "87d349ed-44d7-43e1-9a83-5f2406dee5bd",
      "displayName": "John Smith",
      "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1"
    }
  },
  "body": {
    "contentType": "html",
    "content": "<p>Hello world</p>"
  },
  "channelIdentity": null,
  "attachments": [
    
  ],
  "mentions": [
    
  ],
  "onBehalfOf": null,
  "policyViolation": null,
  "reactions": [
    
  ],
  "messageHistory": [
    
  ],
  "replies": [
    
  ],
  "hostedContents": [
    
  ],
  "eventDetail": null
}

Les notifications de modification sont parfois remises dans le désordre, car elles sont asynchrones. Si votre application nécessite que les ressources soient triées dans un ordre particulier, veillez à trier le contenu déchiffré selon la propriété appropriée. Par exemple, si les messages doivent être affichés par ordre chronologique dans votre application de conversation, triez les chatMessages déchiffrés par createdDateTime.

Lorsqu’un message de conversation est modifié, une notification de modification est envoyée pour la modification, avec une mise à jour lastEditedDateTime. Votre application de conversation doit afficher le message modifié au lieu du message d’origine, si l’intention est d’afficher la dernière version des messages.

Les remarques sur contentType, les images, la protection contre la perte de données (DLP) et les stratégies de rétention dans Étape 4 : Récupérer les messages s’appliquent également aux messages déchiffrés.

Étape 8 : Renouveler les abonnements aux notifications de modification

Pour des raisons de sécurité, les abonnements à chatMessage expirent dans 60 minutes. Nous vous recommandons de renouveler toutes les 30 minutes pour autoriser une certaine mémoire tampon. Les notifications de cycle de vie pour les abonnements arrivant à expiration ne sont pas disponibles actuellement ; Par conséquent, vous devez suivre les abonnements et les renouveler avant qu’ils n’expirent en mettant à jour la propriété expirationDateTime , comme décrit dans Mettre à jour l’abonnement. Étant donné que le renouvellement de milliers d’abonnements prend du temps, il s’agit d’une raison d’éviter les notifications de modification par conversation.

Si un abonnement expire avant d’être renouvelé, certaines notifications de modification peuvent être manquées. Resynchroniser les messages en répétant l’étape 4 : Récupérer les messages.

L’exemple suivant montre comment renouveler un abonnement.

Demande

PATCH https://graph.microsoft.com/v1.0/subscriptions/88aa8a88-88a8-88a8-8888-88a8aa88a88a
Content-type: application/json

{
   "expirationDateTime":"2023-01-12T18:23:45.9356913Z"
}

Réponse

HTTP/1.1 200 OK
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#subscriptions/$entity",
    "id": "88aa8a88-88a8-88a8-8888-88a8aa88a88a",
    "resource": "/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/getAllMessages",
    "applicationId": "aa8aaaa8-8aa8-88a8-888a-aaaa8a8aa88a",
    "changeType": "created",
    "clientState": null,
    "notificationUrl": "https://function-ms-teams-subscription-webhook-z2a2ig2bfq-uc.a.run.app",
    "notificationQueryOptions": null,
    "lifecycleNotificationUrl": null,
    "expirationDateTime": "2023-01-12T18:23:45.9356913Z",
    "creatorId": "8888a8a8-8a88-888a-88aa-8a888a88888a",
    "includeResourceData": null,
    "latestSupportedTlsVersion": "v1_2",
    "encryptionCertificate": null,
    "encryptionCertificateId": null,
    "notificationUrlAppId": null
}

Étape 9 : Obtenir et définir des points de vue

Un point de vue dans une conversation marque l’horodatage auquel la conversation a été lue pour la dernière fois par les utilisateurs, afin que les utilisateurs puissent voir que les messages sous le point de vue ne sont pas lus.

Pour obtenir le point de vue d’une conversation, utilisez la GET méthode HTTP sur la ressource chats , comme illustré dans l’exemple suivant.

Demande

GET https://graph.microsoft.com/v1.0/users/87d349ed-44d7-43e1-9a83-5f2406dee5bd/chats/19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2

Réponse

HTTP/1.1 200 OK
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity",
    "id": "19:b1234aaa12345a123aa12aa12aaaa1a9@thread.v2",
    "topic": null,
    "createdDateTime": "2023-01-11T01:34:18.929Z",
    "lastUpdatedDateTime": "2023-01-11T01:34:18.929Z",
    "chatType": "group",
    "webUrl": "https://teams.microsoft.com/l/chat/19%3Ab1234aaa12345a123aa12aa12aaaa1a9%40thread.v2/0?tenantId=4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1",
    "tenantId": "4dc1fe35-8ac6-4f0d-904a-7ebcd364bea1",
    "onlineMeetingInfo": null,
    "viewpoint": {
        "isHidden": false,
        "lastMessageReadDateTime": "2021-05-27T22:13:01.577Z"
    }
}

Le point de vue d’une conversation pour un utilisateur est mis à jour chaque fois que l’utilisateur marque la conversation comme lue, marque la conversation comme non lue, masque la conversation ou désactive la conversation.

Estimation des coûts

Actuellement, la récupération des messages par utilisateur et par conversation (étape 4) n’implique pas de frais de consommation (mais présente des limites de limitation). Seules les notifications de modification ont des frais de consommation de 0,00075 USD par message.

Si votre application compte 50 utilisateurs et que chaque utilisateur reçoit des messages de 20 utilisateurs et envoie 300 messages par mois, le coût approximatif est le suivant :

  • 50 destinataires x (20 expéditeurs x 300 messages/mois/expéditeur)/destinataire x 0,00075 $/message = 300 000 messages/mois x 0,00075 $/message = 225 $/mois.

Pour obtenir les informations de tarification les plus récentes, consultez Licences et conditions de paiement de l’API Microsoft Teams.