Publier des messages avec un webhook
Un webhook est une URL qui publie dans ta communauté. Envoie-lui du JSON depuis tout ce qui sait faire une requête HTTP, comme une CI, une supervision, une tâche cron ou un script, et le message apparaît dans le salon.
Ce que vous pouvez faire
- Publier du texte ou une carteDu texte simple, ou une carte avec un titre, une couleur, du markdown, des champs et des images.
- Joindre des fichiersJusqu’à cinq fichiers par message : logs, rapports, captures d’écran.
- Ajouter des boutonsDes liens, ou des boutons qui modifient la carte ou contactent ton service.
- Le modifier ensuiteLa réponse contient une URL de callback pour mettre à jour ou supprimer le message.
Dans l'app
Une requête depuis la CI, une carte dans #deploys. Le nom en haut est celui que tu as donné au webhook.
Démarrage rapide
Crée le webhook
Dans l’application de bureau, ouvre Gérer le serveur → Webhooks dans ta communauté, crée un webhook, choisis les salons où il peut publier et copie l’URL d’un salon. Elle a cette forme :
urlhttps://mss.gs/api/v1/webhook/{webhook_guid}/{token}/{channel_guid}Envoie un message
Signe le JSON avec le secret du webhook et envoie-le en POST. Un webhook créé dans l’application de bureau en a toujours un : copie-le depuis Secret du webhook dans les réglages du webhook.
bashBODY='{"content": "Build #1847 passed on main"}' SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$MSSGS_WEBHOOK_SECRET" | sed 's/^.* //') curl -X POST "$MSSGS_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -H "X-Mssgs-Signature: sha256=$SIG" \ -d "$BODY"Lis la réponse
La réponse te donne l’id du message, et une
callback_urlpour modifier le message plus tard.json{ "success": true, "message_id": "aZZ1a2b-...", "callback_url": "https://mss.gs/api/v1/trigger-callback/8f14e45f-...", "stream_url": "https://mss.gs/api/v1/instant/6b1e...c0?token=a1b2c3d4-..." }
Ce que tu peux envoyer
Un message prend soit la forme courte (du texte avec un titre et une couleur), soit celle d’une carte complète, et les deux peuvent porter des boutons et des fichiers. Le nom en haut de la carte est toujours celui du webhook. Dans les réglages du webhook, tu décides aussi s’il peut publier des images et mentionner des personnes.
Forme courte
Suffisante pour la plupart des alertes.
{
"content": "Server backup completed at 03:00 UTC",
"color": "green",
"title": "Backup Bot"
}| Champ | Type | Ce qu’il fait |
|---|---|---|
content | string | Le texte du message. Obligatoire, sauf si tu envoies une carte ou des fichiers. |
color | string | blue (par défaut), green, orange, red, yellow ou purple. |
title | string | Un titre au-dessus du texte. Par défaut, le nom du webhook. |
Une carte complète
Envoie un message_container pour une carte avec un titre cliquable, un sous-titre, du markdown, des champs et des images. Via un webhook, une carte accepte type, color, title, title_url, sub_title, description, fields, avatar_url, image_url, image_base64, images et les champs de chargement. La pastille de statut, le badge, les stats de diff et le raisonnement replié sont réservés aux réponses de commande. Chaque champ est décrit sur cartes de message.
{
"message_container": {
"type": "embed_message",
"color": "green",
"title": "Build #1847 passed",
"title_url": "https://ci.example.com/builds/1847",
"description": "All 212 tests green on **main**.",
"fields": [
{ "field": "Duration", "value": "2m 34s" },
{ "field": "Commit", "value": "1a2b3c4" }
]
}
}Boutons
Ajoute un tableau actions pour placer des boutons sous le message. Leur fonctionnement est expliqué sur boutons.
Fichiers
Publie de vrais fichiers avec un message : un log, un rapport, une capture d’écran. Ils s’affichent comme n’importe quelle pièce jointe, en ligne de téléchargement, ou intégrés pour les images, la vidéo et l’audio. Un message composé uniquement de fichiers est valide : omets content et la carte.
{
"message_container": {
"color": "orange",
"title": "Log dump: ios",
"description": "DMs stopped arriving after switching networks"
},
"attachments": [
{
"name": "mssgs-logs-20260803-141205.log",
"content_base64": "MjAyNi0wOC0wMyAxNDoxMjowNSBbV1NdIGNvbm5lY3RlZAo=",
"mime_type": "text/plain"
}
]
}| Champ | Type | Ce qu’il fait |
|---|---|---|
name | string | Le nom sous lequel le fichier se télécharge. Obligatoire. Un chemin est réduit à sa dernière partie. |
content_base64 | string | Les octets du fichier en base64, bruts ou sous forme d’URI data:. mssgs stocke le fichier et ne garde qu’un lien sur le message. |
mime_type | string | Le type de contenu de content_base64. Par défaut text/plain. |
url | string | Un fichier déjà hébergé sur mssgs : un chemin /static/... ou une URL https://mss.gs/.... |
content_base64 ou url. Les deux, ou aucun des deux, provoquent une erreur.| Limite | Valeur |
|---|---|
| Fichiers par message | 5 |
| Taille par fichier, après décodage | 8 Mo |
| Nom de fichier | 200 caractères |
| Requête entière | Environ 10 Mo. Le base64 grossit un fichier d’un tiers, donc un fichier seul de plus de 7 Mo environ ne passera pas. |
Pourquoi url n’accepte que des adresses mssgs
Une URL de webhook finit souvent collée dans d’autres tableaux de bord. Une URL qui a fuité ne doit pas permettre à quelqu’un de faire télécharger à l’application de chaque membre un fichier depuis un serveur de son choix. Si ton fichier se trouve ailleurs, envoie-le en content_base64 et mssgs l’héberge.
Un envoi raté ne fait pas échouer le message
Les fichiers sont vérifiés d’abord, mais envoyés ensuite. Si un envoi échoue, ce fichier est laissé de côté et le reste du message est quand même publié, sans erreur : mieux vaut perdre le fichier que le rapport. Si un fichier compte, vérifie qu’il est bien arrivé.
Un fichier depuis la ligne de commande
BODY='{"attachments":[{"name":"report.csv","content_base64":"'"$(base64 < report.csv | tr -d '\n')"'","mime_type":"text/csv"}]}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$MSSGS_WEBHOOK_SECRET" | sed 's/^.* //')
printf '%s' "$BODY" | curl -sS -X POST "$MSSGS_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-H "X-Mssgs-Signature: sha256=$SIG" \
--data-binary @-Signer les requêtes
Un webhook doté d’un secret n’accepte que les requêtes qui prouvent le connaître, et un webhook créé dans l’application de bureau en a toujours un (Secret du webhook dans ses réglages). Signe le corps brut de la requête en HMAC-SHA256 avec le secret, et envoie le condensé hexadécimal en minuscules dans l’en-tête X-Mssgs-Signature, sous la forme sha256=<hex>.
import crypto from 'node:crypto';
const body = JSON.stringify({ content: 'Deploy finished' });
const signature = crypto.createHmac('sha256', process.env.MSSGS_WEBHOOK_SECRET)
.update(body)
.digest('hex');
await fetch(process.env.MSSGS_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Mssgs-Signature': `sha256=${signature}`
},
body
});401 et {"error": "INVALID_SIGNATURE"}. Seul un webhook sans secret, par exemple créé via MCP sans webhook_secret, accepte les requêtes non signées.L’en-tête X-Hub-Signature-256 de GitHub est accepté aussi, donc un webhook GitHub avec le même secret fonctionne tel quel. La signature ne contient pas d’horodatage : elle n’empêche donc pas qu’une requête interceptée soit renvoyée. C’est l’URL qui reste le secret qui compte.
Réponses et erreurs
Un message publié revient avec son id et une callback_url pour le mettre à jour ou le supprimer pendant 30 minutes.
{
"success": true,
"message_id": "aZZ1a2b-...",
"callback_url": "https://mss.gs/api/v1/trigger-callback/8f14e45f-...",
"stream_url": "https://mss.gs/api/v1/instant/6b1e...c0?token=a1b2c3d4-..."
}error dans le corps. Considère tout corps qui contient une clé error comme un échec, quel que soit le code de statut.HTTP/1.1 200 OK
Content-Type: application/json
{ "error": "ATTACHMENT_TOO_LARGE" }const res = await fetch(webhookUrl, { method: 'POST', headers, body });
const reply = await res.json().catch(() => null);
// A rejected payload still comes back as 200: read the body.
if (!res.ok || (reply && reply.error)) {
throw new Error(`webhook rejected: ${reply?.error ?? res.status}`);
}Quand le message a des boutons, la réponse contient aussi une stream_url : un flux en direct des réponses, réactions et appuis sur les boutons de ce message, ouvert pendant 10 minutes, ou une heure si tu envoies "sse_event_extended_timeout": true. Voir mises à jour en direct.
Codes de statut
| Statut | Quand |
|---|---|
401 | Le webhook a un secret et la signature manque ou est fausse. |
403 | Ce webhook n’a pas le droit de publier dans ce salon. |
404 | Il n’y a pas de webhook à cette URL. |
413 | La requête est trop volumineuse. |
429 | Trop de requêtes. Ralentis et réessaie. |
502 | Le message n’a pas pu être livré. Réessaie. |
Codes d’erreur
| Code | Signification |
|---|---|
MISSING_CONTENT | Rien à publier : ni texte, ni carte, ni fichiers. |
INVALID_MESSAGE_CONTAINER | message_container n’est pas un objet. |
INVALID_MESSAGE_CONTAINER_TYPE | Le type de carte n’est ni embed_message ni system_message. |
MISSING_MESSAGE_CONTAINER_DESCRIPTION | Une carte a besoin d’une description, sauf s’il s’agit d’une carte de chargement. |
MISSING_MESSAGE_CONTAINER_LOADER_TEXT | Une carte de chargement a besoin de loader_text. |
INVALID_WEBHOOK_BINDING | Ce webhook n’a pas le droit de publier dans ce salon. |
INVALID_SIGNATURE | L’en-tête de signature manque ou est faux. |
REQUEST_BODY_TOO_LARGE | La requête dépasse la limite de taille. |
PUBLISH_FAILED | Le message n’a pas pu être livré. |
INVALID_ACTIONS_FORMAT, INVALID_ACTION_MISSING_FIELDS, DUPLICATE_ACTION_ID, INVALID_TRIGGERS_FORMAT, INVALID_TRIGGER_MISSING_ACTION, INVALID_TRIGGER_ACTION_NOT_ALLOWED | Un bouton pose problème. Voir boutons. |
Erreurs de fichiers
| Code | Signification |
|---|---|
INVALID_ATTACHMENTS_FORMAT | attachments n’est pas une liste, ou une entrée n’est pas un objet. |
TOO_MANY_ATTACHMENTS | Plus de cinq fichiers. |
MISSING_ATTACHMENT_NAME | Un fichier n’a pas de nom. |
INVALID_ATTACHMENT_NAME | Le nom se réduit à rien d’utilisable, comme ... |
MISSING_ATTACHMENT_SOURCE | Ni url ni content_base64. |
AMBIGUOUS_ATTACHMENT_SOURCE | À la fois url et content_base64. |
INVALID_ATTACHMENT_BASE64 | Le base64 ne se décode pas. |
ATTACHMENT_TOO_LARGE | Un fichier dépasse 8 Mo après décodage. |
INVALID_ATTACHMENT_URL | L’url n’est pas une adresse mssgs. |
Limites
| Limite | Valeur |
|---|---|
| Taille de la requête | Environ 10 Mo |
| Fichiers par message | 5, de 8 Mo maximum chacun |
| Description de la carte | Jusqu’à 50 000 octets. Au-delà de 1 000 octets, les membres voient le début et un bouton Afficher plus. |
| Modifier le message ensuite | 30 minutes, via callback_url |
| Flux en direct d’un message avec boutons | 10 minutes, ou une heure sur demande |
Les requêtes sont soumises à une limite de débit. Quand tu reçois un 429, attends avant de renvoyer, et regroupe en un seul message les alertes qui arrivent en rafale.
GitHub, UniFi et App Store Connect
Fais pointer l’un de ces services vers une URL de webhook : mssgs le reconnaît et publie une vraie carte, sans payload à écrire. Voir intégrations. Ces services reçoivent en réponse {"success": true}, sans URL de callback.
| Source | Reconnue par | Ce qu’elle publie |
|---|---|---|
| GitHub | L’en-tête x-github-event | Pushs, pull requests et revues, issues et commentaires, branches et tags, releases. Une rafale de changements sur une même issue ou pull request est regroupée en une seule carte. |
| UniFi Protect | Le user agent protect-alarm-manager | Coups de sonnette, mouvements, et personnes, véhicules ou colis détectés par tes caméras. |
| App Store Connect | Son corps de notification ou l’en-tête x-apple-signature | Les notifications d’App Store Connect. |