---
title: "Webhooks: publica mensajes en un canal de mssgs"
description: "Envía un mensaje o una tarjeta a un canal de mssgs desde CI, monitorización o un script con una petición HTTP. Formatos, archivos, firma, errores y límites."
canonical: https://docs.mss.gs/es/webhooks
language: es
---

# Publica mensajes con un webhook

Un webhook es una URL que publica en tu comunidad. Envíale JSON desde cualquier cosa capaz de hacer una petición HTTP, como CI, monitorización, un cron job o un script, y el mensaje aparece en el canal.

## Qué puedes hacer

- **Publica texto o una tarjeta** Texto sin formato, o una tarjeta con título, color, markdown, campos e imágenes.

- **Adjunta archivos** Hasta cinco archivos por mensaje: logs, informes, capturas de pantalla.

- **Añade botones** Enlaces, o botones que cambian la tarjeta o llaman a tu servicio.

- **Cámbialo después** La respuesta incluye una callback URL para actualizar o borrar el mensaje.

En la app

#### Build #1847 superado

Una petición desde CI, una tarjeta en #deploys. El nombre de arriba es el que le diste al webhook.

- [Inicio rápido](#quick-start)

- [Qué puedes enviar](#format)

- [Archivos](#attachments)

- [Firma](#signing)

- [Respuestas y errores](#responses)

- [Límites](#limits)

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

## Inicio rápido

### Crea el webhook

En la app de escritorio, abre **Manage Server → Webhooks** en tu comunidad, crea un webhook, elige los canales en los que puede publicar y copia la URL de un canal. Tiene esta forma:

```url
https://mss.gs/api/v1/webhook/{webhook_guid}/{token}/{channel_guid}
```

### Envía un mensaje

Firma el JSON con el secreto del webhook y envíalo con POST. Un webhook creado en la app de escritorio siempre tiene uno: cópialo de **Webhook Secret** en los ajustes del 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"
```

### Lee la respuesta

La respuesta te da el id del mensaje y una callback_url para cambiar el mensaje más tarde.

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

## Qué puedes enviar

Un mensaje es o bien la forma corta (texto con un título y un color) o bien una tarjeta completa, y cualquiera de las dos puede llevar botones y archivos. El nombre de arriba de la tarjeta es siempre el nombre del propio webhook. En los ajustes del webhook también decides si puede publicar imágenes y mencionar a personas.

### Forma corta

Suficiente para la mayoría de las alertas.

```json
{
  "content": "Server backup completed at 03:00 UTC",
  "color": "green",
  "title": "Backup Bot"
}
```

| Campo | Tipo | Qué hace |
| --- | --- | --- |
| content | string | El texto del mensaje. Obligatorio salvo que envíes una tarjeta o archivos. |
| color | string | blue (por defecto), green , orange , red , yellow o purple . |
| title | string | Un título encima del texto. Por defecto, el nombre del webhook. |

### Una tarjeta completa

Envía un message_container para una tarjeta con título enlazado, subtítulo, markdown, campos e imágenes. A través de un webhook, una tarjeta admite type , color , title , title_url , sub_title , description , fields , avatar_url , image_url , image_base64 , images y los campos del loader. La píldora de estado, el distintivo, las estadísticas del diff y el razonamiento plegado son para respuestas a comandos. Todos los campos están en [tarjetas de mensaje](https://docs.mss.gs/es/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 superado

### Botones

Añade un array actions para poner botones bajo el mensaje. Cómo funcionan se explica en [botones](https://docs.mss.gs/es/buttons).

## Archivos

Publica archivos de verdad con un mensaje: un log, un informe, una captura de pantalla. Se muestran como cualquier otro adjunto, como una fila de descarga o dentro del mensaje en el caso de imágenes, vídeo y audio. Un mensaje solo con archivos también vale: omite content y la tarjeta.

```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 | Qué hace |
| --- | --- | --- |
| name | string | El nombre con el que se descarga el archivo. Obligatorio. Una ruta se reduce a su última parte. |
| content_base64 | string | Los bytes del archivo en base64, sin más o como URI data: . mssgs guarda el archivo y deja solo un enlace en el mensaje. |
| mime_type | string | El tipo de contenido de content_base64 . Por defecto, text/plain . |
| url | string | Un archivo ya alojado en mssgs: una ruta /static/... o una URL https://mss.gs/... . |

| Límite | Valor |
| --- | --- |
| Archivos por mensaje | **5** |
| Tamaño por archivo, una vez decodificado | **8 MB** |
| Nombre de archivo | 200 caracteres |
| Petición completa | Unos 10 MB. Base64 hace que un archivo ocupe un tercio más, así que un solo archivo de más de unos 7 MB no cabe. |

### Por qué url solo acepta direcciones de mssgs

Una URL de webhook suele acabar pegada en otros paneles. Si se filtra, no debe permitir que alguien haga que la app de cada miembro descargue un archivo de un servidor que haya elegido él. Si tu archivo está en otro sitio, envíalo como content_base64 y mssgs lo aloja.

### Una subida fallida no hace fallar el mensaje

Los archivos se comprueban de antemano pero se suben después. Si una subida falla, ese archivo se queda fuera y el resto del mensaje se publica igualmente, sin error: mejor perder el archivo que el informe. Si un archivo es importante, comprueba que ha llegado.

### Un archivo desde la línea de comandos

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

## Firmar las peticiones

Un webhook con secreto solo acepta peticiones que demuestren que lo conocen, y un webhook creado en la app de escritorio siempre tiene uno (**Webhook Secret** en sus ajustes). Firma el cuerpo sin procesar de la petición con HMAC-SHA256 usando el secreto, y envía el digest hexadecimal en minúsculas en la cabecera 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
});
```

También se acepta la cabecera propia de GitHub X-Hub-Signature-256 , así que un webhook de GitHub con el mismo secreto funciona tal cual. La firma no lleva marca de tiempo, así que no impide que una petición interceptada se vuelva a enviar: el secreto que de verdad importa sigue siendo la URL.

## Respuestas y errores

Un mensaje publicado vuelve con su id y una callback_url para actualizarlo o borrarlo 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}`);
}
```

Cuando el mensaje tiene botones, la respuesta incluye también una stream_url : un stream en directo de las respuestas, reacciones y pulsaciones de botón en ese mensaje, abierto durante 10 minutos, o una hora si envías "sse_event_extended_timeout": true . Consulta [actualizaciones en directo](https://docs.mss.gs/es/live-updates).

### Códigos de estado

| Estado | Cuándo |
| --- | --- |
| 401 | El webhook tiene secreto y la firma falta o es incorrecta. |
| 403 | Este webhook no puede publicar en ese canal. |
| 404 | No hay ningún webhook en esta URL. |
| 413 | La petición es demasiado grande. |
| 429 | Demasiadas peticiones. Ve más despacio y vuelve a intentarlo. |
| 502 | No se ha podido entregar el mensaje. Vuelve a intentarlo. |

### Códigos de error

| Código | Significado |
| --- | --- |
| MISSING_CONTENT | Nada que publicar: ni texto, ni tarjeta, ni archivos. |
| INVALID_MESSAGE_CONTAINER | message_container no es un objeto. |
| INVALID_MESSAGE_CONTAINER_TYPE | El tipo de tarjeta no es embed_message ni system_message . |
| MISSING_MESSAGE_CONTAINER_DESCRIPTION | Una tarjeta necesita una descripción, salvo que sea un loader. |
| MISSING_MESSAGE_CONTAINER_LOADER_TEXT | Una tarjeta de loader necesita loader_text . |
| INVALID_WEBHOOK_BINDING | Este webhook no puede publicar en ese canal. |
| INVALID_SIGNATURE | La cabecera de firma falta o es incorrecta. |
| REQUEST_BODY_TOO_LARGE | La petición supera el límite de tamaño. |
| PUBLISH_FAILED | No se ha podido entregar el mensaje. |
| INVALID_ACTIONS_FORMAT , INVALID_ACTION_MISSING_FIELDS , DUPLICATE_ACTION_ID , INVALID_TRIGGERS_FORMAT , INVALID_TRIGGER_MISSING_ACTION , INVALID_TRIGGER_ACTION_NOT_ALLOWED | Algo falla en un botón. Consulta [botones](https://docs.mss.gs/es/buttons). |

### Errores de archivos

| Código | Significado |
| --- | --- |
| INVALID_ATTACHMENTS_FORMAT | attachments no es una lista, o una entrada no es un objeto. |
| TOO_MANY_ATTACHMENTS | Más de cinco archivos. |
| MISSING_ATTACHMENT_NAME | Un archivo no tiene nombre. |
| INVALID_ATTACHMENT_NAME | Del nombre no queda nada utilizable, como con .. . |
| MISSING_ATTACHMENT_SOURCE | Ni url ni content_base64 . |
| AMBIGUOUS_ATTACHMENT_SOURCE | url y content_base64 a la vez. |
| INVALID_ATTACHMENT_BASE64 | El base64 no se puede decodificar. |
| ATTACHMENT_TOO_LARGE | Un archivo supera los 8 MB una vez decodificado. |
| INVALID_ATTACHMENT_URL | La url no es una dirección de mssgs. |

## Límites

| Límite | Valor |
| --- | --- |
| Tamaño de la petición | Unos 10 MB |
| Archivos por mensaje | 5, de hasta 8 MB cada uno |
| Descripción de la tarjeta | Hasta 50.000 bytes. A partir de 1.000 bytes, los miembros ven el principio y un botón **Show more**. |
| Actualizar el mensaje después | 30 minutos, mediante callback_url |
| Stream en directo de un mensaje con botones | 10 minutos, o una hora si lo pides |

Las peticiones tienen un límite de frecuencia. Cuando recibas un 429 , espera antes de volver a enviar, y agrupa en un solo mensaje las alertas que llegan en ráfagas.

## GitHub, UniFi y App Store Connect

Apunta uno de estos servicios a una URL de webhook y mssgs lo reconoce y publica una tarjeta en condiciones, sin payload que escribir. Consulta [integraciones](https://mss.gs/es/integrations). Estos reciben como respuesta {"success": true} y ninguna callback URL.

| Origen | Se reconoce por | Qué publica |
| --- | --- | --- |
| GitHub | La cabecera x-github-event | Pushes, pull requests y revisiones, issues y comentarios, ramas y etiquetas, releases. Una ráfaga de cambios en una misma issue o pull request se agrupa en una sola tarjeta. |
| UniFi Protect | El user agent protect-alarm-manager | Timbrazos, movimiento, y personas, vehículos o paquetes detectados por tus cámaras. |
| App Store Connect | El cuerpo de sus notificaciones o la cabecera x-apple-signature | Notificaciones de App Store Connect. |

## Sigue construyendo
