---
title: "Webhooks: publique mensagens num canal do mssgs"
description: "Envie uma mensagem ou um cartão a um canal do mssgs do CI, do monitoramento ou de um script, com uma requisição HTTP. Formatos, arquivos, assinatura e limites."
canonical: https://docs.mss.gs/pt/webhooks
language: pt
---

# 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ão** Texto simples, ou um cartão com título, cor, markdown, campos e imagens.

- **Anexe arquivos** Até cinco arquivos por mensagem: logs, relatórios, capturas de tela.

- **Adicione botões** Links, ou botões que mudam o cartão ou chamam o seu serviço.

- **Mude depois** A resposta traz uma callback URL para atualizar ou apagar a mensagem.

No app

#### Build #1847 passou

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](#quick-start)

- [O que você pode enviar](#format)

- [Arquivos](#attachments)

- [Assinatura](#signing)

- [Respostas e erros](#responses)

- [Limites](#limits)

- [GitHub, UniFi, App Store](#special)

## 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:

```url
https://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.

```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"
```

### 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-..."
}
```

## 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"
}
```

| 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](https://docs.mss.gs/pt/bots).

```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" }
    ]
  }
}
```

#### Build #1847 passou

### Botões

Adicione um array actions para colocar botões embaixo da mensagem. Como eles funcionam está em [botões](https://docs.mss.gs/pt/buttons).

## 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"
    }
  ]
}
```

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

| 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

```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
});
```

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-..."
}
```

```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](https://docs.mss.gs/pt/live-updates).

### 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](https://docs.mss.gs/pt/buttons). |

### 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](https://mss.gs/pt/integrations). 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. |

## Continue criando
