---
title: "API Documentation - mssgs | Build Webhooks, Bots & Integrations"
description: "mssgs API documentation. Build integrations and bots with webhooks, custom triggers and the REST API. Including examples, error codes and interactive actions."
canonical: https://mss.gs/en/docs
language: en
---

# Webhooks & Integrations

Build bots, automations and integrations with mssgs webhooks and triggers

#### Bot Messages

Rich embed cards, interactive buttons and live-updating messages for bots.

#### Release notes

New features and fixes in every mssgs release.

## Getting Started

With mssgs webhooks you can send messages to channels and build interactive bots that respond to commands.

#### Create a webhook

Go to Server Settings > Integrations and create a new webhook or trigger.

#### Copy the URL

Copy the webhook URL or trigger GUID for your integration.

#### Start building

Send JSON payloads to the webhook URL to post messages.

#### Two ways to integrate

**Incoming Webhooks**: Send messages to mssgs from external tools (CI/CD, monitoring, etc.) **Webhook Triggers**: Build interactive bots that respond to commands and button clicks.

## Incoming Webhooks

Send messages to a channel via a webhook URL. Perfect for CI/CD notifications, monitoring alerts, and other automations.

### Request structure

Send a POST request with your message payload as JSON body to the webhook URL. Generate the URL in the app under **Manage Server → Webhooks**; the URL itself authenticates the request, so you don't send a token in the body.

```json
{
  "content": "Hello from my integration!",
  "color": "green",
  "title": "My Bot"
}
```

### Simple Format

The simplest way to send a message.

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

| Field | Type | Description |
| --- | --- | --- |
| content | string | Message text required |
| color | string | blue , green , orange , red , yellow , purple |
| title | string | Title above the message |
| title_url | string | Makes the title a clickable link |
| sub_title | string | Smaller text below the title |
| avatar_url | string | Avatar image URL |

### Full Format (message_container)

For more control over the message, use the full message_container object.

```json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "title": "Order Update",
    "description": "Your order #12345 has been shipped!",
    "title_url": "https://example.com/orders/12345",
    "sub_title": "Estimated delivery: Tomorrow",
    "bot_name": "Order Bot",
    "fields": [
      { "field": "Status", "value": "Shipped" },
      { "field": "Tracking", "value": "ABC123456" }
    ]
  },
  "actions": [...]
}
```

| Field | Type | Description |
| --- | --- | --- |
| type | string | embed_message (default) or system_message |
| color | string | Accent color of the message |
| title | string | Title of the message |
| description | string | Main text required |
| title_url | string | Link behind the title |
| sub_title | string | Subtitle text |
| bot_name | string | Custom bot name |
| avatar_url | string | Avatar image |
| fields | array | Key-value fields |

### Fields

Add structured key-value data to your message.

```json
{
  "fields": [
    { "field": "Status", "value": "Completed" },
    { "field": "Duration", "value": "2m 34s" },
    { "field": "Environment", "value": "Production" }
  ]
}
```

## Build your embed

Edit the fields or the JSON payload. Both stay in sync. Watch the message render exactly as it will in a channel. This is the real webhook body; copy it when it looks right.

## Webhook Triggers

Webhook triggers let you connect external services to your server. When a user types a matching command (e.g. /help ) or clicks an action button, the backend POSTs to your webhook URL. Your webhook responds with JSON to post a message back to the channel.

#### User types a command

A user sends a message starting with your trigger's prefix, e.g. /help

#### mssgs calls your webhook

The backend sends a POST request to your configured webhook URL with the message data.

#### You respond with JSON

Your server returns a JSON response with the message content, embeds and actions.

#### Message appears in channel

mssgs displays the response as a message in the channel.

### Request Your Webhook Receives

When a user sends a message that starts with your trigger's trigger_match prefix:

```json
{
  "server_guid": "abc12345-...",
  "channel_guid": "def67890-...",
  "trigger_match": "/help",
  "message": {
    "id": "d01ZZdef6-xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "content": "/help how do I create a channel?",
    "member_guid": "member-guid-here",
    "user_guid": "user-guid-here",
    "cms": 1234567890123,
    "group_guids": ["group-guid-1", "group-guid-2"]
  },
  "callback_url": "https://mss.gs/api/v1/trigger-callback/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "stream_url": "https://mss.gs/api/v1/instant/6b1e...c0?token=a1b2c3d4-..."
}
```

#### Request via Action Button

When triggered via a trigger:{guid} action button, the content is always "[Action Triggered]" and includes the action's payload :

```json
{
  "server_guid": "abc12345-...",
  "channel_guid": "def67890-...",
  "trigger_match": "/help",
  "message": {
    "id": "d01ZZdef6-xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "content": "[Action Triggered]",
    "member_guid": "member-guid-here",
    "user_guid": "user-guid-here",
    "cms": 1234567890123,
    "group_guids": ["group-guid-1", "group-guid-2"],
    "is_action_button": true,
    "action_payload": {
      "custom_key": "custom_value"
    }
  },
  "callback_url": "https://mss.gs/api/v1/trigger-callback/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "stream_url": "https://mss.gs/api/v1/instant/6b1e...c0?token=a1b2c3d4-..."
}
```

#### Request Fields

| Field | Type | Description |
| --- | --- | --- |
| server_guid | string | The server where the trigger was fired |
| channel_guid | string | The channel where the message was sent |
| trigger_match | string | The prefix that matched (e.g. /help ) |
| message | object | The message that triggered the webhook. For action-button triggers this is a synthetic "[Action Triggered]" message, not the message the button was on |
| message.id | string | Unique message ID. For action-button triggers it is freshly generated for the synthetic message — it is *not* the ID of the message the button was on (that ID never reaches the webhook body) |
| message.content | string | Full message text (command match) or "[Action Triggered]" (action button) |
| message.member_guid | string | The server member who triggered it |
| message.user_guid | string | The user's global GUID |
| message.group_guids | array | The server group GUIDs the member belongs to (use to check roles like admin) |
| message.cms | number | Timestamp in milliseconds |
| message.is_action_button | boolean | true when triggered via an action button (not present for command matches) |
| message.action_payload | object | Custom data from the action button (only present for action triggers) |
| callback_url | string | Unique URL to update or delete the response message (valid for 30 minutes) |
| stream_url | string | Unique instant SSE stream for live interactions (replies, reactions, button presses) with your response message. Open it to listen; valid for 10 minutes |

### Response Format

Your webhook must respond with 200 status and a JSON body. The response becomes a message in the channel.

#### Simple response

```json
{
  "content": "Hello! How can I help you?"
}
```

#### Embed response

```json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "title": "Help",
    "description": "Here's how to create a channel...",
    "title_url": "https://docs.example.com/channels",
    "sub_title": "Channel Guide",
    "avatar_url": "https://example.com/bot-avatar.png"
  }
}
```

#### Full response with actions

```json
{
  "content": "Here's what I found:",
  "message_container": {
    "type": "embed_message",
    "color": "green",
    "title": "Search Results",
    "description": "Found 3 matching items.",
    "title_url": "https://example.com/results",
    "sub_title": "Query: channels",
    "avatar_url": "https://example.com/bot-avatar.png"
  },
  "actions": [
    {
      "type": "button",
      "text": "View Details",
      "color": "blue",
      "triggers": [
        {
          "action": "ws:send",
          "payload": {
            "method": "USER_REQUESTED_TRIGGER",
            "server_guid": "server-guid",
            "channel_guid": "channel-guid",
            "trigger_guid": "details-trigger-guid",
            "action": { "result_id": "42" }
          }
        }
      ]
    },
    {
      "text": "Open Docs",
      "type": "url:https://docs.example.com"
    }
  ]
}
```

#### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| content | string | Plain text message content |
| message_container | object | Rich embed/system message display |
| message_container.type | string | embed_message (default) or system_message |
| message_container.color | string | blue , green , orange , red (default: blue ) |
| message_container.title | string | Embed title |
| message_container.description | string | Embed body text |
| message_container.title_url | string | Makes the title a clickable link |
| message_container.sub_title | string | Smaller text below the title |
| message_container.avatar_url | string | Avatar image URL shown alongside the embed |
| actions | array | Action buttons (see [Actions](#actions)) |

At least one of content , message_container , or actions must be present; otherwise no message is created.

The message author is your trigger's post_app_name (configured in server settings). If not set, the server name is used.

#### Timeout

Your webhook must respond within **5 seconds**. If it times out, an error message is shown to the user. Use the [callback URL](#callback-url) if you need more time to process.

## Actions

Actions are interactive buttons displayed below your message. There are three kinds: url:{url} (open a link), trigger:{guid} (fire a real webhook trigger), and webhook_action (server-driven effects; recommended for interactive buttons).

### Action Fields

| Field | Type | Description |
| --- | --- | --- |
| type | string | "webhook_action" (server-driven), or shorthand "trigger:{guid}" / "url:{url}" required |
| text | string | Button display text (also accepts label ) required |
| color | string | green , blue , purple (primary) or red , orange , yellow (secondary) |
| id | string | For webhook_action : stable button id, sent back on press so the backend knows which button fired |
| payload | object | Data passed back on press ( trigger:{guid} and webhook_action ) |
| triggers | array | Server-side effect steps run when a webhook_action button is pressed |

### Simple Shorthands

For basic actions, use the type shorthand:

| Type | Description |
| --- | --- |
| trigger:{guid} | Fires another webhook trigger by its GUID. The trigger receives message.action_payload with the action's payload . |
| url:{url} | Opens the URL in the user's browser |

```json
{
  "actions": [
    {
      "text": "Check Status",
      "type": "trigger:abc123-trigger-guid",
      "payload": { "order_id": "12345" }
    },
    {
      "text": "View Order",
      "type": "url:https://example.com/orders/12345"
    }
  ]
}
```

#### What happens when an action is clicked?

When the user clicks "Check Status", the target trigger's webhook receives a new request with message.content set to "[Action Triggered]" , message.is_action_button set to true , and message.action_payload set to { "order_id": "12345" } .

### Interactive Buttons (server-driven)

A webhook_action button is handled **by the backend**; no real trigger required. On press, the client sends a WEBHOOK_MESSAGE_ACTION command carrying the message id, the button's id , and its payload . The backend then relays the press to the message's instant SSE stream (if a device is listening), runs the button's server-side triggers steps, and broadcasts the result to **everyone** in the channel. Because those steps are read from the stored message (never from the client), a member can only run what you defined.

#### Server-side trigger steps

A button's triggers array lists the effect steps that run when it's pressed, in order. They execute on the backend and broadcast to everyone in the channel:

| action | Effect (broadcast to everyone) | Fields |
| --- | --- | --- |
| update_message | Replace the container / buttons; edit text, recolor or swap buttons, remove buttons ( actions: [] ), or swap the image | message_container , actions , content |
| remove_message | Delete the message | None |
| add_reaction | Add a reaction, as the pressing member | emoji |
| add_reply | Post a reply, as the pressing member | content |

Steps run in order and are best-effort; a failing step is logged and the rest still run. To fire a *real* trigger instead, use the trigger:{guid} shorthand, not triggers .

#### Example: self-updating "Open Gate" button

Everyone gets instant broadcast feedback from the server-side steps; the edge device sets the authoritative final state via the callback URL once the gate physically opens.

```json
{
  "message_container": { "color": "yellow", "title": "Front Door", "description": "Doorbell rang" },
  "actions": [
    {
      "type": "webhook_action",
      "id": "open_gate",
      "text": "Open Gate",
      "color": "green",
      "payload": { "btn": "open_gate" },
      "triggers": [
        {
          "action": "update_message",
          "message_container": { "color": "blue", "title": "Front Door", "description": "Opening..." },
          "actions": [ { "type": "webhook_action", "id": "opening", "text": "Opening...", "color": "blue" } ]
        },
        { "action": "add_reaction", "emoji": ":white_check_mark:" }
      ]
    }
  ]
}
```

#### What happens when "Open Gate" is pressed

1. Everyone sees the button become "Opening..." and a checkmark reaction appear; the server-side steps, broadcast by the backend (not just the presser). 2. Your edge device listening on the instant stream receives the press, opens the gate, then PUT s the callback_url to set the authoritative final state ("Opened").

#### Removed: client-only steps

The old client-only local:update_message / local:remove_message steps are gone. Use the server-side update_message / remove_message steps instead; they broadcast to everyone, not just the presser. A press on a message that no longer exists returns MESSAGE_NOT_FOUND .

#### Rate limit

Users are rate-limited to prevent spam when firing triggers.

## Callback URL

Every webhook request includes a callback_url : a unique, token-authenticated URL that your service can call to **update** or **delete** the response message after it was posted. The URL is valid for **30 minutes**.

#### Use cases

- Updating a "processing..." message with final results

- Showing live progress (e.g. build status, deployment)

- Removing a message after it's no longer relevant

### Update Message

```json
{
  "content": "Updated text content",
  "message_container": {
    "color": "green",
    "title": "Build Complete",
    "description": "All 42 tests passed."
  },
  "actions": []
}
```

All fields are optional; only include the ones you want to change. To remove action buttons, send "actions": [] .

### Delete Message

No request body needed. The message is permanently deleted from the channel.

### Responses

```javascript
// 200 OK
{ "success": true }

// 404 Not Found (callback token expired or invalid)
{ "error": "TOKEN_NOT_FOUND" }

// 400 Bad Request (no update fields provided)
{ "error": "MISSING_FIELDS" }
```

### Example: Progress Updates

```javascript
// 1. Your webhook responds immediately with a "loading" message
// Response:
{
  "message_container": {
    "color": "blue",
    "title": "Deploying...",
    "description": "Starting deployment to production."
  }
}

// 2. Your service updates the message as progress continues
// PUT {callback_url}
{
  "message_container": {
    "color": "blue",
    "title": "Deploying...",
    "description": "Step 2/3: Running migrations."
  }
}

// 3. Final update when done
// PUT {callback_url}
{
  "message_container": {
    "color": "green",
    "title": "Deploy Complete",
    "description": "v2.1.0 is now live on production."
  },
  "actions": [
    {
      "label": "View Logs",
      "type": "url:https://example.com/deploys/123/logs"
    }
  ]
}
```

#### Callback URL lifetime

Valid for **30 minutes** from when the trigger fires. After that, update/delete requests return 404 . Once you delete the message, the callback URL is consumed and cannot be used again.

### See it happen

The full trigger loop, live: command, JSON reply, button tap, callback update.

##### A command fires your webhook

Someone types /deploy. mssgs POSTs the message to your endpoint, including a callback_url for this message.

##### Your reply becomes a card

Respond with JSON and the card appears in the channel: title, description, status and buttons.

##### A button tap comes back to you

The tap fires the trigger to your server again, with a fresh callback_url.

##### You update the card in place

First a loader while you work, then the final state. Everyone in the channel sees it update live.

#### Deploy v2.1.0 to production?

#### Deploy complete

## MCP: AI Assistants

The mssgs desktop app ships a built-in **MCP server** (Model Context Protocol), so AI assistants like Claude can read and act in your workspace. It runs locally on your machine and talks to your logged-in mssgs session; no separate bot account or hosting needed.

Authenticated with a Bearer token from the app's [AI / MCP settings page](https://mss.gs/en/help/bots-mcp/connect-your-ai): enable the server, pick the channels to expose and press "Copy config"; the copied block already contains your port and token. The server listens on localhost only (default port 7444) and runs while mssgs is open and you are signed in.

```bash
claude mcp add --transport http mssgs http://127.0.0.1:7444/mcp \
  --header "Authorization: Bearer <your-token>"
```

### Real-time events over SSE

Open the MCP endpoint as a stream ( GET /mcp with Accept: text/event-stream ) and you receive JSON-RPC notifications as they happen:

| Notification | Fires on |
| --- | --- |
| notifications/mssgs/message | New messages in channels you can see |
| notifications/mssgs/bot_event | Button presses, reactions and replies on messages your assistant posted via send_bot_message |
| notifications/mssgs/presence | Presence changes |
| notifications/mssgs/typing | Typing indicators |
| notifications/mssgs/call | Voice call events |

#### Interactive bot cards from your assistant

Post an embed card with buttons via send_bot_message , listen for bot_event on the stream (or poll get_bot_events ), and update the card in place with edit_message . Same card model as the [bot messages](https://mss.gs/en/docs/bots) docs.

### Bot tools

| Tool | Description |
| --- | --- |
| send_bot_message | Post a message or embed card (with buttons) as your assistant |
| get_bot_events | Fetch queued button presses, reactions and replies on your bot messages |
| edit_message | Update a posted message's message_container in place |
| set_bot_status | Set the assistant's presence/status |

Bot tools are available when you are signed in as a bot account. The full tool list (channels, search, files, calls and more) is documented in the app on the [AI / MCP settings page](https://mss.gs/en/help/bots-mcp/connect-your-ai).

## Errors

When errors occur, you receive a JSON error response.

```json
{
  "error": "ERROR_CODE"
}
```

### Error codes

| Code | Description |
| --- | --- |
| INVALID_TOKEN | Invalid authentication token |
| MISSING_REQUIRED_FIELDS | Required fields are missing in data |
| MISSING_CONTENT | Message requires content or message_container.description |
| TOKEN_NOT_FOUND | Callback token expired or invalid |
| MISSING_FIELDS | No update fields provided in callback request |
| UNSUPPORTED_GITHUB_EVENT | GitHub event type not supported |
| UNSUPPORTED_UNIFI_PROTECT_EVENT | UniFi Protect event not supported |

### Success response

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

Use callback_url to [update or delete](#callback-url) the message later. When your message includes action buttons, the response also contains a stream_url ; an instant SSE stream for live interactions (replies, reactions, button presses) with that message.

## Examples

### Simple notification

```bash
curl -X POST https://mss.gs/api/v1/webhook/SERVER_GUID/CHANNEL_GUID \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Build completed successfully!"
  }'
```

### Colored alert with link

```json
{
  "content": "Build #123 completed!",
  "color": "green",
  "title": "CI/CD Pipeline",
  "title_url": "https://github.com/org/repo/actions/runs/123"
}
```

### Error alert

```json
{
  "message_container": {
    "type": "system_message",
    "color": "red",
    "title": "Alert: Database Error",
    "description": "Connection failed: timeout after 30s",
    "sub_title": "prod-db-01"
  }
}
```

### Deployment approval with actions

```json
{
  "message_container": {
    "color": "yellow",
    "title": "Deployment Request",
    "description": "User @johndoe requested a deployment to production."
  },
  "actions": [
    {
      "label": "Approve",
      "type": "trigger:approve-deploy-trigger-guid",
      "payload": { "deploy_id": "dep_123", "env": "production" }
    },
    {
      "label": "Reject",
      "type": "trigger:reject-deploy-trigger-guid",
      "payload": { "deploy_id": "dep_123" }
    },
    {
      "label": "View Changes",
      "type": "url:https://github.com/org/repo/compare/main...deploy"
    }
  ]
}
```

### Trigger response: private message

```json
{
  "message_container": {
    "description": "This is a private response only you can see."
  },
  "visible_to_member_guids": ["<member_guid from request>"],
  "ephemeral": true
}
```

### Trigger response: interactive menu

```json
{
  "message_container": {
    "title": "What would you like to do?",
    "description": "Choose an option below:"
  },
  "actions": [
    {
      "label": "Get Help",
      "type": "trigger:help-trigger-guid"
    },
    {
      "label": "View Stats",
      "type": "trigger:stats-trigger-guid",
      "payload": { "period": "weekly" }
    }
  ]
}
```

## Special Webhooks

mssgs automatically recognizes certain webhook types.

### GitHub Webhooks

Automatically detected via the x-github-event header. Supported events: Push, Pull Requests, Issues, Releases, and more.

### UniFi Protect Webhooks

Automatically detected via user-agent: protect-alarm-manager header.

## Notes

- **Timeout:** Your webhook must respond within **5 seconds**. If it times out, an error message is shown to the user. Use the callback URL if you need more time to process.

- **Message persistence:** Trigger response messages are saved to the database and appear in channel history.

- **Callback URL lifetime:** Valid for 30 minutes from when the trigger fires. After that, update/delete requests return 404 .

- **Rate limiting:** Users are rate-limited to prevent spam when firing triggers.

## Questions?

We're happy to help you with your integration.
