Incoming Webhooks

Incoming webhooks let external systems push content into Monotree — chat messages, wall posts, or calendar entries. They use a token embedded in the URL, so no Authorization header is needed. Configure each webhook in the Open API → Incoming Webhooks tab, or programmatically via the Manage Incoming Webhooks API.

POST https://customer.monotree.com/api/open/v1/incoming/{token}

Actions

A single webhook is configured with one of these actions:

ActionEffect
chat_messageSends a message to a group chat. 1:1 chats and AI chats are refused.
direct_messageSends a message to a single user's private chat, as a bot.
wall_postCreates a post on a wall.
calendar_entryAdds an entry to the calendar.

direct_message is delivered to one user's private chat by a bot user and is configured with a target user id. It always requires a bot user as the sender. Its payload is the same simple text field (plus optional media). Create and manage these webhooks at scale via Manage Incoming Webhooks, or in the CMS Incoming Webhooks tab.

Content is posted as the webhook's bot user. A webhook set not to use a bot posts as the user who created it, or as the system user once that user has been deleted. A chat_message or direct_message webhook created over the API always uses a bot.

A webhook created over the API keeps working only while the token that created it holds the action's write scope (write:chat_messages for chat_message and direct_message, write:posts for wall_post, write:calendar_entries for calendar_entry). While the token lacks it, every call answers 403. It also stops with its token: revoking or deleting the token revokes the webhook, and while the token is expired every call answers 403. Webhooks created in the CMS have no token and are not checked.

Payload

For chat_message, direct_message and wall_post, the payload is a simple text field. text is required, also when you send media, and holds at most 50,000 characters:

{
  "text": "Alert: Kitchen cooler temperature is 12°C"
}

For calendar_entry, send a structured object:

{
  "title": "Maintenance window",
  "starts_at": "2026-05-01",
  "starts_time": "08:00",
  "ends_at": "2026-05-01",
  "ends_time": "10:00",
  "calendar_event_type_id": 2
}

Only title (at most 255 characters) and starts_at are required for calendar_entry. starts_time and ends_time are HH:MM on a 24-hour clock, and ends_at may not be before starts_at. calendar_event_type_id picks the event type; when omitted, the published event type named "Default" is used — if that type has been renamed or unpublished, it is required and a missing one returns 422.

The entry is published at once with no groups, so every employee sees it. Only the six fields above are read: body, groups and any other field are dropped without an error. To make an entry with a description or for chosen groups, use POST /calendar-entries.

wall_post answers 403 while the walls feature is switched off.

Attaching media

chat_message, direct_message and wall_post can include images, videos, or documents via a media array of {url, type} pairs. Because webhooks have no bearer token, the two-step upload + media_ids flow used by the regular API does not apply — instead, Monotree fetches each URL and creates the Media records server-side.

{
  "text": "Image post from webhook",
  "media": [
    { "url": "https://example.com/photo.jpg", "type": "image" },
    { "url": "https://example.com/report.pdf", "type": "document" }
  ]
}

Rules

  • wall_post accepts a mix of images, videos and documents. chat_message and direct_message take one type per payload; mixed types return 422.
  • Maximum 10 items per payload.
  • URLs must use https, carry no credentials, and point at a host that resolves to public addresses only. A URL may redirect up to 3 times, and every hop is checked the same way.
  • Each file can be at most 100 MB. An empty file is refused.
  • Per-URL download timeout: 15 seconds.
  • Failed downloads → 422 with a descriptive error, and nothing is posted.

Response

A successful call answers 200 with ok and the created item under data:

{ "ok": true, "data": { "id": 5531, "room_id": 240 } }
Actiondata
chat_message, direct_message{ id, room_id }: the chat message and the chat it was sent to.
wall_post{ id, wall_id }: the post and its wall.
calendar_entry{ id, title, starts_at }, with starts_at as YYYY-MM-DD.

Errors

Send Accept: application/json so that every error comes back as JSON. The bodies with an error key are JSON either way. Without the header, the others change:

  • an invalid payload (the 422 with "title": "The given data was invalid.") answers 302 with a Location of the request's Referer, or the server's root URL when there is none, and no error body. A client that follows redirects lands on that page instead of seeing an error.
  • the title-shaped 422, 404 and 429 and any 500 come back as an HTML error page with the same status code.

The call is logged as failed in both cases.

StatusWhenBody
404The token is unknown, or the webhook is paused or revoked.{ "error": "not_found", "message": "Invalid or inactive webhook." }
422The webhook's action type is not one Monotree supports.{ "error": "invalid_action", "message": "Unknown webhook action type." }
403The webhook was created over the API and its token lacks the action's scope.{ "error": "forbidden", "message": "The token that created this webhook lacks the write:posts scope." }
403The webhook was created over the API and its token is expired.{ "error": "forbidden", "message": "The token that created this webhook is revoked or expired." }
403A wall_post while the walls feature is switched off.{ "error": "forbidden", "message": "The walls feature is not enabled." }
422The payload is invalid: text missing or too long, a bad date or time, too many media items.{ "status": 422, "code": 0, "title": "The given data was invalid.", "message": "The text field is required." }
422A media URL could not be downloaded or was refused.{ "error": "validation_error", "message": "The given media payload was invalid.", "errors": { "media.url": "Could not download media from URL: … (Only https links can be imported.)" } }
422Mixed media types for chat_message or direct_message.{ "error": "validation_error", "message": "The given data was invalid.", "errors": { "media": "All media items must be of the same type." } }
422The chat_message room is no longer a group chat.{ "title": "Validation failed.", "message": "Messages can only be sent to group chats.", "status": 422, "code": 0 }
404The webhook's target room, wall or user no longer exists, or a direct_message target is no longer a regular, registered user (deactivated, for example).{ "title": "Entity does not exist.", "message": "Not Found", "status": 404, "code": 0 }
429More calls from your IP address in a minute than the rate limit allows (see below).{ "title": "ThrottleRequestsException", "message": "Too Many Attempts.", "status": 429, "code": 0 }

Every call to an active webhook that is not rate-limited, failed or not, is logged on the webhook in the CMS Incoming Webhooks tab.

Examples

# chat_message / direct_message / wall_post
curl -X POST https://customer.monotree.com/api/open/v1/incoming/YOUR_TOKEN \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"text": "Alert: Kitchen cooler temperature is 12°C"}'
# wall_post with image
curl -X POST https://customer.monotree.com/api/open/v1/incoming/YOUR_TOKEN \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"text": "See photo", "media": [{"url": "https://example.com/cooler.jpg", "type": "image"}]}'