Aller au contenu principal
Développeurs Webhooks

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

$ curl -X POST "$MSSGS_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-Mssgs-Signature: sha256=$SIG" \
-d '{"message_container": {"title": "Build #1847 réussi", ...}}'
{"success": true, "message_id": "aZZ1a2b-..."}

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

  1. 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 :

    url
    https://mss.gs/api/v1/webhook/{webhook_guid}/{token}/{channel_guid}
  2. 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.

    bash
    BODY='{"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"
  3. Lis la réponse

    La réponse te donne l’id du message, et une callback_url pour 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-..."
    }
Toute personne qui a l’URL et le secret peut publier dans ce salon. Garde-les tous les deux hors des dépôts publics et du code côté client.

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.

json
{
  "content": "Server backup completed at 03:00 UTC",
  "color": "green",
  "title": "Backup Bot"
}
ChampTypeCe qu’il fait
contentstringLe texte du message. Obligatoire, sauf si tu envoies une carte ou des fichiers.
colorstringblue (par défaut), green, orange, red, yellow ou purple.
titlestringUn 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.

json
{
  "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" }
    ]
  }
}
deploys
System
Message de CI

Build #1847 réussi

Les 212 tests sont au vert sur main.
Duration
2m 34s
Commit
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.

json
{
  "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"
    }
  ]
}
ChampTypeCe qu’il fait
namestringLe nom sous lequel le fichier se télécharge. Obligatoire. Un chemin est réduit à sa dernière partie.
content_base64stringLes 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_typestringLe type de contenu de content_base64. Par défaut text/plain.
urlstringUn fichier déjà hébergé sur mssgs : un chemin /static/... ou une URL https://mss.gs/....
Envoie exactement l’un des deux par fichier, content_base64 ou url. Les deux, ou aucun des deux, provoquent une erreur.
LimiteValeur
Fichiers par message5
Taille par fichier, après décodage8 Mo
Nom de fichier200 caractères
Requête entièreEnviron 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

bash
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>.

javascript
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
});
Une requête sans signature valide reçoit 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.

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-..."
}
Lis le corps, pas seulement le statut. Un payload refusé porte sa raison sous forme de code error dans le corps. Considère tout corps qui contient une clé error comme un échec, quel que soit le code de statut.
http
HTTP/1.1 200 OK
Content-Type: application/json

{ "error": "ATTACHMENT_TOO_LARGE" }
javascript
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

StatutQuand
401Le webhook a un secret et la signature manque ou est fausse.
403Ce webhook n’a pas le droit de publier dans ce salon.
404Il n’y a pas de webhook à cette URL.
413La requête est trop volumineuse.
429Trop de requêtes. Ralentis et réessaie.
502Le message n’a pas pu être livré. Réessaie.

Codes d’erreur

CodeSignification
MISSING_CONTENTRien à publier : ni texte, ni carte, ni fichiers.
INVALID_MESSAGE_CONTAINERmessage_container n’est pas un objet.
INVALID_MESSAGE_CONTAINER_TYPELe type de carte n’est ni embed_message ni system_message.
MISSING_MESSAGE_CONTAINER_DESCRIPTIONUne carte a besoin d’une description, sauf s’il s’agit d’une carte de chargement.
MISSING_MESSAGE_CONTAINER_LOADER_TEXTUne carte de chargement a besoin de loader_text.
INVALID_WEBHOOK_BINDINGCe webhook n’a pas le droit de publier dans ce salon.
INVALID_SIGNATUREL’en-tête de signature manque ou est faux.
REQUEST_BODY_TOO_LARGELa requête dépasse la limite de taille.
PUBLISH_FAILEDLe 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_ALLOWEDUn bouton pose problème. Voir boutons.

Erreurs de fichiers

CodeSignification
INVALID_ATTACHMENTS_FORMATattachments n’est pas une liste, ou une entrée n’est pas un objet.
TOO_MANY_ATTACHMENTSPlus de cinq fichiers.
MISSING_ATTACHMENT_NAMEUn fichier n’a pas de nom.
INVALID_ATTACHMENT_NAMELe nom se réduit à rien d’utilisable, comme ...
MISSING_ATTACHMENT_SOURCENi url ni content_base64.
AMBIGUOUS_ATTACHMENT_SOURCEÀ la fois url et content_base64.
INVALID_ATTACHMENT_BASE64Le base64 ne se décode pas.
ATTACHMENT_TOO_LARGEUn fichier dépasse 8 Mo après décodage.
INVALID_ATTACHMENT_URLL’url n’est pas une adresse mssgs.

Limites

LimiteValeur
Taille de la requêteEnviron 10 Mo
Fichiers par message5, de 8 Mo maximum chacun
Description de la carteJusqu’à 50 000 octets. Au-delà de 1 000 octets, les membres voient le début et un bouton Afficher plus.
Modifier le message ensuite30 minutes, via callback_url
Flux en direct d’un message avec boutons10 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.

SourceReconnue parCe qu’elle publie
GitHubL’en-tête x-github-eventPushs, 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 ProtectLe user agent protect-alarm-managerCoups de sonnette, mouvements, et personnes, véhicules ou colis détectés par tes caméras.
App Store ConnectSon corps de notification ou l’en-tête x-apple-signatureLes notifications d’App Store Connect.

Continuer