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:
| Action | Effect |
|---|---|
chat_message | Sends a message to a group chat. 1:1 chats and AI chats are refused. |
direct_message | Sends a message to a single user's private chat, as a bot. |
wall_post | Creates a post on a wall. |
calendar_entry | Adds 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_postaccepts a mix of images, videos and documents.chat_messageanddirect_messagetake one type per payload; mixed types return422.- 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 →
422with 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 } }| Action | data |
|---|---|
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
422with"title": "The given data was invalid.") answers302with aLocationof the request'sReferer, 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-shaped422,404and429and any500come back as an HTML error page with the same status code.
The call is logged as failed in both cases.
| Status | When | Body |
|---|---|---|
404 | The token is unknown, or the webhook is paused or revoked. | { "error": "not_found", "message": "Invalid or inactive webhook." } |
422 | The webhook's action type is not one Monotree supports. | { "error": "invalid_action", "message": "Unknown webhook action type." } |
403 | The 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." } |
403 | The webhook was created over the API and its token is expired. | { "error": "forbidden", "message": "The token that created this webhook is revoked or expired." } |
403 | A wall_post while the walls feature is switched off. | { "error": "forbidden", "message": "The walls feature is not enabled." } |
422 | The 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." } |
422 | A 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.)" } } |
422 | Mixed 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." } } |
422 | The 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 } |
404 | The 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 } |
429 | More 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"}]}'