Pular para o conteúdo principal
Desenvolvedores Webhooks

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

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

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

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

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

    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. Leia a resposta

    A resposta traz o id da mensagem e uma callback_url para 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-..."
    }
Qualquer pessoa com a URL e o secret pode publicar nesse canal. Mantenha os dois fora de repositórios públicos e de código no lado do cliente.

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.

json
{
  "content": "Server backup completed at 03:00 UTC",
  "color": "green",
  "title": "Backup Bot"
}
CampoTipoO que faz
contentstringO texto da mensagem. Obrigatório, a menos que você envie um cartão ou arquivos.
colorstringblue (padrão), green, orange, red, yellow ou purple.
titlestringUm 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.

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 from CI

Build #1847 passou

Todos os 212 testes verdes em main.
Duration
2m 34s
Commit
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.

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"
    }
  ]
}
CampoTipoO que faz
namestringO nome com que o arquivo é baixado. Obrigatório. Um caminho é reduzido à última parte.
content_base64stringOs bytes do arquivo em base64, puros ou como URI data:. O mssgs guarda o arquivo e deixa só um link na mensagem.
mime_typestringO content type de content_base64. O padrão é text/plain.
urlstringUm arquivo já hospedado no mssgs: um caminho /static/... ou uma URL https://mss.gs/....
Envie exatamente um de content_base64 ou url por arquivo. Os dois, ou nenhum, é um erro.
LimiteValor
Arquivos por mensagem5
Tamanho por arquivo, depois de decodificado8 MB
Nome do arquivo200 caracteres
Requisição inteiraCerca 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

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 @-

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

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
});
Uma requisição sem assinatura válida recebe 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.

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-..."
}
Leia o corpo, não só o status. Um payload rejeitado traz o motivo como um código error no corpo. Trate qualquer corpo com a chave error como falha, seja qual for o código de status.
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}`);
}

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

StatusQuando
401O webhook tem um secret e a assinatura está faltando ou errada.
403Este webhook não pode publicar nesse canal.
404Não há webhook nesta URL.
413A requisição é grande demais.
429Requisições demais. Vá mais devagar e tente de novo.
502A mensagem não pôde ser entregue. Tente de novo.

Códigos de erro

CódigoSignificado
MISSING_CONTENTNada para publicar: nem texto, nem cartão, nem arquivos.
INVALID_MESSAGE_CONTAINERmessage_container não é um objeto.
INVALID_MESSAGE_CONTAINER_TYPEO tipo do cartão não é embed_message nem system_message.
MISSING_MESSAGE_CONTAINER_DESCRIPTIONUm cartão precisa de uma descrição, a menos que seja um loader.
MISSING_MESSAGE_CONTAINER_LOADER_TEXTUm cartão de loader precisa de loader_text.
INVALID_WEBHOOK_BINDINGEste webhook não pode publicar nesse canal.
INVALID_SIGNATUREO header de assinatura está faltando ou errado.
REQUEST_BODY_TOO_LARGEA requisição passa do limite de tamanho.
PUBLISH_FAILEDA 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_ALLOWEDHá algo errado com um botão. Veja botões.

Erros de arquivo

CódigoSignificado
INVALID_ATTACHMENTS_FORMATattachments não é uma lista, ou um item não é um objeto.
TOO_MANY_ATTACHMENTSMais de cinco arquivos.
MISSING_ATTACHMENT_NAMEUm arquivo não tem nome.
INVALID_ATTACHMENT_NAMEO nome não se reduz a nada utilizável, como ...
MISSING_ATTACHMENT_SOURCENem url nem content_base64.
AMBIGUOUS_ATTACHMENT_SOURCETanto url quanto content_base64.
INVALID_ATTACHMENT_BASE64O base64 não decodifica.
ATTACHMENT_TOO_LARGEUm arquivo passa de 8 MB depois de decodificado.
INVALID_ATTACHMENT_URLA url não é um endereço do mssgs.

Limites

LimiteValor
Tamanho da requisiçãoCerca de 10 MB
Arquivos por mensagem5, de até 8 MB cada
Descrição do cartãoAté 50.000 bytes. Passando de 1.000 bytes, os membros veem o começo e um botão Show more.
Atualizar a mensagem depois30 minutos, pela callback_url
Stream ao vivo de uma mensagem com botões10 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.

OrigemReconhecido porO que publica
GitHubO header x-github-eventPushes, 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 ProtectO user agent protect-alarm-managerToques de campainha, movimento, e pessoas, veículos ou pacotes detectados pelas suas câmeras.
App Store ConnectO corpo das notificações dele ou o header x-apple-signatureNotificações do App Store Connect.

Continue criando