Publique mensagens com um webhook
Um webhook é uma URL que publica na sua comunidade. Mande JSON para ela a partir de qualquer coisa que faça uma requisição HTTP, como CI, monitoramento, um cron job ou um script, e a mensagem aparece no canal.
O que você pode fazer
- Publique texto ou um cartãoTexto simples, ou um cartão com título, cor, markdown, campos e imagens.
- Anexe arquivosAté cinco arquivos por mensagem: logs, relatórios, capturas de tela.
- Adicione botõesLinks, ou botões que mudam o cartão ou chamam o seu serviço.
- Mude depoisA resposta traz uma callback URL para atualizar ou apagar a mensagem.
No app
Uma requisição do CI, um cartão em #deploys. O nome no topo é o nome que você deu ao webhook.
Início rápido
Crie o webhook
No app para computador, abra Manage Server → Webhooks da sua comunidade, crie um webhook, escolha os canais em que ele pode publicar e copie a URL de um canal. Ela tem este formato:
urlhttps://mss.gs/api/v1/webhook/{webhook_guid}/{token}/{channel_guid}Envie uma mensagem
Assine o JSON com o secret do webhook e faça um POST. Um webhook criado no app para computador sempre tem um: copie-o de Webhook Secret nas configurações do 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"Leia a resposta
A resposta traz o id da mensagem e uma
callback_urlpara mudar a mensagem depois.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-..." }
O que você pode enviar
Uma mensagem é a forma curta (texto com título e cor) ou um cartão completo, e as duas podem levar botões e arquivos. O nome no topo do cartão é sempre o nome do próprio webhook. Nas configurações do webhook você também decide se ele pode publicar imagens e mencionar pessoas.
Forma curta
Suficiente para a maioria dos alertas.
{
"content": "Server backup completed at 03:00 UTC",
"color": "green",
"title": "Backup Bot"
}| Campo | Tipo | O que faz |
|---|---|---|
content | string | O texto da mensagem. Obrigatório, a menos que você envie um cartão ou arquivos. |
color | string | blue (padrão), green, orange, red, yellow ou purple. |
title | string | Um título acima do texto. O padrão é o nome do webhook. |
Um cartão completo
Envie um message_container para ter um cartão com título em link, subtítulo, markdown, campos e imagens. Por um webhook, um cartão aceita type, color, title, title_url, sub_title, description, fields, avatar_url, image_url, image_base64, images e os campos do loader. A pílula de status, o selo, as estatísticas de diff e o raciocínio recolhido são para respostas a comandos. Todos os campos estão em cartões de mensagem.
{
"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" }
]
}
}Botões
Adicione um array actions para colocar botões embaixo da mensagem. Como eles funcionam está em botões.
Arquivos
Publique arquivos de verdade com uma mensagem: um log, um relatório, uma captura de tela. Eles aparecem como qualquer outro anexo, numa linha de download ou, no caso de imagens, vídeo e áudio, direto na mensagem. Uma mensagem só com arquivos também vale: deixe de fora content e o cartão.
{
"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"
}
]
}| Campo | Tipo | O que faz |
|---|---|---|
name | string | O nome com que o arquivo é baixado. Obrigatório. Um caminho é reduzido à última parte. |
content_base64 | string | Os bytes do arquivo em base64, puros ou como URI data:. O mssgs guarda o arquivo e deixa só um link na mensagem. |
mime_type | string | O content type de content_base64. O padrão é text/plain. |
url | string | Um arquivo já hospedado no mssgs: um caminho /static/... ou uma URL https://mss.gs/.... |
content_base64 ou url por arquivo. Os dois, ou nenhum, é um erro.| Limite | Valor |
|---|---|
| Arquivos por mensagem | 5 |
| Tamanho por arquivo, depois de decodificado | 8 MB |
| Nome do arquivo | 200 caracteres |
| Requisição inteira | Cerca de 10 MB. O base64 deixa um arquivo um terço maior, então um arquivo único acima de uns 7 MB não cabe. |
Por que url só aceita endereços do mssgs
A URL de um webhook costuma acabar colada em outros painéis. Se ela vazar, não pode permitir que alguém faça o app de cada membro baixar um arquivo de um servidor escolhido por essa pessoa. Se o seu arquivo está em outro lugar, envie como content_base64 e o mssgs hospeda.
Um upload que falha não derruba a mensagem
Os arquivos são verificados antes, mas enviados depois. Se um upload falhar, esse arquivo fica de fora e o resto da mensagem é publicado mesmo assim, sem erro: perder o arquivo é melhor do que perder o relatório. Se um arquivo for importante, confira se ele chegou.
Um arquivo pela linha de comando
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 @-Assinatura das requisições
Um webhook com secret só aceita requisições que provem que o conhecem, e um webhook criado no app para computador sempre tem um (Webhook Secret nas configurações dele). Assine o corpo bruto da requisição com HMAC-SHA256 usando o secret e envie o digest em hexadecimal minúsculo no header X-Mssgs-Signature como 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 e {"error": "INVALID_SIGNATURE"}. Só um webhook sem secret, como um criado via MCP sem webhook_secret, aceita requisições sem assinatura.O header próprio do GitHub, X-Hub-Signature-256, também é aceito, então um webhook do GitHub com o mesmo secret funciona como está. A assinatura não tem timestamp, então não impede que uma requisição capturada seja reenviada: a URL continua sendo o segredo que importa.
Respostas e erros
Uma mensagem publicada volta com o id dela e uma callback_url para atualizá-la ou apagá-la durante 30 minutos.
{
"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 no corpo. Trate qualquer corpo com a chave error como falha, seja qual for o código de status.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}`);
}Quando a mensagem tem botões, a resposta também traz uma stream_url: um stream ao vivo das respostas, reações e toques em botões nessa mensagem, aberto por 10 minutos, ou por uma hora se você enviar "sse_event_extended_timeout": true. Veja atualizações ao vivo.
Códigos de status
| Status | Quando |
|---|---|
401 | O webhook tem um secret e a assinatura está faltando ou errada. |
403 | Este webhook não pode publicar nesse canal. |
404 | Não há webhook nesta URL. |
413 | A requisição é grande demais. |
429 | Requisições demais. Vá mais devagar e tente de novo. |
502 | A mensagem não pôde ser entregue. Tente de novo. |
Códigos de erro
| Código | Significado |
|---|---|
MISSING_CONTENT | Nada para publicar: nem texto, nem cartão, nem arquivos. |
INVALID_MESSAGE_CONTAINER | message_container não é um objeto. |
INVALID_MESSAGE_CONTAINER_TYPE | O tipo do cartão não é embed_message nem system_message. |
MISSING_MESSAGE_CONTAINER_DESCRIPTION | Um cartão precisa de uma descrição, a menos que seja um loader. |
MISSING_MESSAGE_CONTAINER_LOADER_TEXT | Um cartão de loader precisa de loader_text. |
INVALID_WEBHOOK_BINDING | Este webhook não pode publicar nesse canal. |
INVALID_SIGNATURE | O header de assinatura está faltando ou errado. |
REQUEST_BODY_TOO_LARGE | A requisição passa do limite de tamanho. |
PUBLISH_FAILED | A mensagem não pôde ser entregue. |
INVALID_ACTIONS_FORMAT, INVALID_ACTION_MISSING_FIELDS, DUPLICATE_ACTION_ID, INVALID_TRIGGERS_FORMAT, INVALID_TRIGGER_MISSING_ACTION, INVALID_TRIGGER_ACTION_NOT_ALLOWED | Há algo errado com um botão. Veja botões. |
Erros de arquivo
| Código | Significado |
|---|---|
INVALID_ATTACHMENTS_FORMAT | attachments não é uma lista, ou um item não é um objeto. |
TOO_MANY_ATTACHMENTS | Mais de cinco arquivos. |
MISSING_ATTACHMENT_NAME | Um arquivo não tem nome. |
INVALID_ATTACHMENT_NAME | O nome não se reduz a nada utilizável, como ... |
MISSING_ATTACHMENT_SOURCE | Nem url nem content_base64. |
AMBIGUOUS_ATTACHMENT_SOURCE | Tanto url quanto content_base64. |
INVALID_ATTACHMENT_BASE64 | O base64 não decodifica. |
ATTACHMENT_TOO_LARGE | Um arquivo passa de 8 MB depois de decodificado. |
INVALID_ATTACHMENT_URL | A url não é um endereço do mssgs. |
Limites
| Limite | Valor |
|---|---|
| Tamanho da requisição | Cerca de 10 MB |
| Arquivos por mensagem | 5, de até 8 MB cada |
| Descrição do cartão | Até 50.000 bytes. Passando de 1.000 bytes, os membros veem o começo e um botão Show more. |
| Atualizar a mensagem depois | 30 minutos, pela callback_url |
| Stream ao vivo de uma mensagem com botões | 10 minutos, ou uma hora se você pedir |
As requisições têm limite de taxa. Quando receber um 429, espere antes de enviar de novo, e junte numa só mensagem os alertas que chegam em rajadas.
GitHub, UniFi e App Store Connect
Aponte um desses serviços para a URL de um webhook e o mssgs o reconhece e publica um cartão caprichado, sem payload para escrever. Veja integrações. Eles recebem {"success": true} como resposta, sem callback URL.
| Origem | Reconhecido por | O que publica |
|---|---|---|
| GitHub | O header x-github-event | Pushes, pull requests e reviews, issues e comentários, branches e tags, releases. Uma rajada de mudanças numa mesma issue ou pull request é reunida num único cartão. |
| UniFi Protect | O user agent protect-alarm-manager | Toques de campainha, movimento, e pessoas, veículos ou pacotes detectados pelas suas câmeras. |
| App Store Connect | O corpo das notificações dele ou o header x-apple-signature | Notificações do App Store Connect. |