Skip to main content

Webhooks & Integrations

Build bots, automations and integrations with mssgs webhooks and triggers

Getting Started

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

1

Create a webhook

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

2

Copy the URL

Copy the webhook URL or trigger GUID for your integration.

3

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.

POST /api/v1/webhook/:server_guid/:channel_guid

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.

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

Simple Format

The simplest way to send a message.

Simple Message
{
  "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.

Full Message Container
{
  "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.

Fields Array
{
  "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.

Presets
Buttons
Preview
Webhook body

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.

1

User types a command

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

2

mssgs calls your webhook

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

3

You respond with JSON

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

4

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:

POST {your_webhook_url}
Command Match Request
{
  "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:

Action Button Request
{
  "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

Simple Response
{
  "content": "Hello! How can I help you?"
}

Embed response

Embed Response
{
  "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

Full Response
{
  "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)

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 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
Shorthand Actions
{
  "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.

webhook_action Example
{
  "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 PUTs 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

PUT {callback_url}
Update Request
{
  "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

DELETE {callback_url}

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

Responses

Callback Responses
// 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

Progress Update Flow
// 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.

1
mssgs → POST your-webhook
A command fires your webhook

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

2
200 ← message_container + actions
Your reply becomes a card

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

3
mssgs → POST your-webhook · [Action Triggered]
A button tap comes back to you

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

4
PUT {callback_url} → 200
You update the card in place

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

# deploys
jasper 18:45 /deploy
System
Message from Deploy Bot• acme/acme-core

Deploy v2.1.0 to production?

Ready
All 42 checks passed. Last commit: 1a2b3c4.
System
Message from Deploy Bot• acme/acme-core
Deploying…Step 2/3: running migrations
System
Message from Deploy Bot• acme/acme-core

Deploy complete

Live
v2.1.0 is now live on production.

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.

MCP http://127.0.0.1:7444/mcp

Authenticated with a Bearer token from the app's AI / MCP settings page: 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.

Connect Claude Code
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/messageNew messages in channels you can see
notifications/mssgs/bot_eventButton presses, reactions and replies on messages your assistant posted via send_bot_message
notifications/mssgs/presencePresence changes
notifications/mssgs/typingTyping indicators
notifications/mssgs/callVoice 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 docs.

Bot tools

Tool Description
send_bot_messagePost a message or embed card (with buttons) as your assistant
get_bot_eventsFetch queued button presses, reactions and replies on your bot messages
edit_messageUpdate a posted message's message_container in place
set_bot_statusSet 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.

Errors

When errors occur, you receive a JSON error response.

Error Response
{
  "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

Success
{
  "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 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

cURL
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 Body
{
  "content": "Build #123 completed!",
  "color": "green",
  "title": "CI/CD Pipeline",
  "title_url": "https://github.com/org/repo/actions/runs/123"
}

Error alert

JSON Body
{
  "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 Body
{
  "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

Private Response
{
  "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

Interactive Menu
{
  "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.

developers@mss.gs