Vous automatisez vos déploiements et souhaitez annoncer chaque release dans un canal Microsoft Teams ? Cette page détaille pas à pas comment publier — via un webhook entrant — une Adaptive Card entièrement dynamique générée par un script Python, tout en évitant les écueils classiques.
Vue d’ensemble de la question
Un script Python déclenche une requête POST vers un webhook entrant Teams. Le corps de la requête contient un objet JSON censé représenter une Adaptive Card et comporte les variables ${releaseTitle}
et ${description}
. Bien que l’appel HTTP renvoie un code 200, la carte affichée dans le canal reste désespérément vide. Le problème provient presque toujours d’un format JSON incomplet ou de la présence de placeholder non résolus : Teams n’interprète pas le langage Adaptive Card Templating. La moindre propriété mal positionnée ou hors version fait disparaître la carte sans message d’erreur visible dans l’interface.
Réponse & solutions — synthèse rapide
Étape | Correctif / bonne pratique | Pourquoi c’est nécessaire |
---|---|---|
Respecter la structure « message → attachments » | { "type": "message", "attachments": [ { "contentType": "application/vnd.microsoft.card.adaptive", "content": { … carte … } } ] } | Teams attend un objet racine de type message et un contentType explicite. Sans ces deux éléments, la carte n’est jamais rendue. |
Rendre les variables côté serveur | Remplacer ${releaseTitle} et ${description} par leurs valeurs avant l’envoi. | Teams ne sait pas évaluer les placeholders template ; si des accolades subsistent, tout le contenu est ignoré. |
Déclarer une version prise en charge | Ajouter par exemple "version": "1.4" dans la carte. | Si vous ciblez une version supérieure à celle activée dans le tenant, Teams masque silencieusement les éléments inconnus. |
Valider le JSON | Tester la carte dans l’Adaptive Card Designer, puis coller le JSON final dans l’appel webhook. | Le concepteur repère instantanément les propriétés invalides ou mal placées. |
Diagnostiquer les erreurs | Ouvrir la console développeur (Ctrl + Shift + I) après l’envoi pour lire les logs. | Les messages JavaScript y indiquent la propriété ou la version incriminée ; précieux pour les cas complexes. |
Alternatives pour un contenu très dynamique | • Microsoft Graph : POST /teams/{id}/channels/{id}/messages avec pièce jointe Adaptive.• Power Automate : flux HTTP « When a HTTP request is received » + action « Post adaptive card ». | Ces méthodes gèrent l’authentification OAuth, autorisent la mise à jour et s’affranchissent de plusieurs limites des webhooks. |
Détaillons chaque bonne pratique
1. Structure « message → attachments »
Le webhook entrant Teams reprend le schéma de la bot framework. Contrairement à d’autres intégrations (Slack, Discord), il ne suffit pas d’envoyer la carte seule ; Teams exige un conteneur type="message"
avec un tableau attachments
. Un oubli fréquent est de placer "type":"AdaptiveCard"
en haut du JSON : cela fonctionne dans l’éditeur, mais pas dans le webhook. Pensez également à aligner les accolades : l’API rejette silencieusement un JSON invalide.
2. Moteur de rendu côté serveur
Adaptive Card Templating (${variable}
, boucles, conditions) n’est pas encore déployé dans Teams pour les messages envoyés par webhook. Vous devez donc « pré‑rendre » la carte : remplacez les placeholders par du texte brut ou échappez-les si vous souhaitez les afficher littéralement. Une bonne pratique consiste à utiliser un mini‑moteur de template (jinja2, string.Template) dans le script Python ; la carte stockée sur disque reste propre et les variables sont injectées au moment de l’appel.
3. Version de carte compatible
La version maximale officiellement supportée dans Teams (août 2025) est 1.4
. La préversion 1.5 peut être activée par un administrateur mais reste optionnelle. Inclure systématiquement la propriété "version"
vous protège d’un futur retour en arrière du côté client. Si vous déployez cross‑tenant, restez sur 1.3
pour une compatibilité maximale.
4. Validation dans l’Adaptive Card Designer
Le Designer vous montre en temps réel le rendu et les erreurs : propriété inconnue, image inaccessible, type inattendu, etc. Exportez toujours le JSON via « Preview Mode → Copy Card JSON » plutôt que via le volet « Code » du Designer, qui laisse parfois des métadonnées internes.
5. Journalisation locale dans Teams
La combinaison Ctrl + Shift + I (ou ⌥ ⌘ I sur macOS) ouvre la console Chromium intégrée. Dans l’onglet Console, filtrez sur « adaptive‑cards ». Les erreurs y apparaissent sous forme de AdaptiveCardParseException
ou AttachmentUnsupportedVersion
. Astuce : ajoutez la clé traceId
dans votre JSON d’appel pour faire ressortir vos propres messages dans la console.
6. Quand passer du webhook à Graph API ?
- Besoin de mettre à jour la carte (statut d’un build, progression d’un pipeline). Le webhook ne sait pas éditer un message existant.
- Contrôle d’accès granulé : Graph API respecte les rôles Azure AD ; un token peut cibler un canal précis.
- Volume élevé : la limite de 28 KB et l’absence de retry policy rendent le webhook fragile pour les intégrations massives.
Exemple complet de script Python
import json, os, requests
from jinja2 import Template
WEBHOOK\_URL = os.environ\["TEAMS\_WEBHOOK"]
CARD\_TEMPLATE = """{
"type": "message",
"attachments": \[{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"\$schema": "[http://adaptivecards.io/schemas/adaptive-card.json](http://adaptivecards.io/schemas/adaptive-card.json)",
"type": "AdaptiveCard",
"version": "1.4",
"body": \[{
"type": "TextBlock",
"size": "Large",
"weight": "Bolder",
"text": "{{ release\_title }}"
},{
"type": "TextBlock",
"wrap": true,
"text": "{{ description }}"
}],
"actions": \[{
"type": "Action.OpenUrl",
"title": "Voir la release",
"url": "{{ release\_url }}"
}]
}
}]
}"""
def post\_release(title: str, description: str, url: str) -> None:
rendered = Template(CARD\_TEMPLATE).render(
release\_title=title, description=description, release\_url=url
)
resp = requests.post(
WEBHOOK\_URL,
headers={"Content-Type": "application/json"},
data=rendered.encode("utf-8"),
timeout=10
)
resp.raise\_for\_status()
print("Carte envoyée ✔︎")
if **name** == "**main**":
post\_release(
title=os.environ\["CI\_RELEASE\_TITLE"],
description=os.environ\["CI\_RELEASE\_NOTES"],
url=os.environ\["CI\_RELEASE\_URL"]
)
Validation et tests
- Test local : utilisez
python -m http.server
pour héberger vos images et vérifiez qu’elles s’affichent bien dans Teams ; le chargement viafile:///
est bloqué. - Test de charge : lancez un loop de 100 appels pour vérifier le temps de rendu et la limitation côté service (throttle ; ~1 req/sec).
- Test d’échec : désactivez votre proxy / VPN pour observer le comportement hors ligne et mettre en place un retry logic.
Dépannage approfondi
Voici les symptômes courants et leurs résolutions détaillées :
Symptôme | Diagnostic | Correctif |
---|---|---|
Carte entièrement vide, mais code 200 retourné | Placeholder non résolus ou propriété au niveau racine manquante | Vérifier les variables, ajouter type: "message" |
Texte sans mise en page, plus de couleurs | La carte est rendue au format markdown fallback | Propriété inconnue : repasser en version 1.4 ou supprimer l’élément |
Image non affichée | URL HTTP au lieu de HTTPS, ou taille > 1024 × 1024 | Passer en HTTPS et réduire la résolution |
Erreur 413 « Payload Too Large » | Carte > 28 KB | Compacter le JSON, héberger les images, supprimer les données inutiles |
Bonnes pratiques de sécurité
- Stocker l’URL de webhook dans un coffre sécurisé (Azure Key Vault, GitHub Secrets, etc.).
- Limiter les permissions réseau sortant : autoriser uniquement les FQDN
*.webhook.office.com
. - Changer immédiatement le webhook en cas de fuite ; il n’y a ni expiration ni liste blanche IP côté Teams.
- Signer le payload : ajoutez un
HMAC-SHA256
maison pour vérifier côté pipeline que personne n’a falsifié la carte avant l’envoi.
Alternatives et scénarios avancés
Graph API + carte mise à jour Envoyez un premier message contenant une carte avec un ID d’action caché (ex. data.idBuild
). Lorsqu’un nouvel événement de pipeline arrive, appelez PATCH /chats/{id}/messages/{id}
et fournissez une nouvelle carte dans attachments[0].content
. Power Automate + Adaptive Card as the Flow Bot Un seul flux HTTP suffit pour recevoir les variables du pipeline, reformater le JSON et publier la carte. Vous bénéficiez du concepteur visuel, d’une gestion automatique des erreurs et d’une trace d’audit dans le run history.
Checklist express avant mise en production
- ✅ JSON validé dans l’Adaptive Card Designer
- ✅ Placeholders rendus côté serveur
- ✅ Propriété
version
cohérente avec votre tenant - ✅ Taille du payload < 28 KB
- ✅ Webhook stocké dans un secret manager
- ✅ Logs d’envoi activés dans le pipeline
- ✅ Procédure de rotation du webhook documentée
Résumé
L’envoi d’une Adaptive Card dynamique dans Teams via webhook est fiable dès lors que :
- le JSON respecte scrupuleusement la structure message → attachments ;
- toutes les variables sont remplacées côté serveur ;
- la version de carte est compatible (
1.4
ou inférieure) ; - le payload est validé et reste léger.
En appliquant ces bonnes pratiques, vous transformez chaque déploiement en notification élégante et actionable, sans effort manuel supplémentaire.