Skip to main content
A bot is an app your organization makes. Installed in a workspace, it gets a token. Added to a channel, it can post there and, if you let it, hear the channel’s new messages at a URL you choose. That makes it two-way: a bot can answer a question, react to a deploy message, or keep a thread up to date. A bot’s posts show the app’s name with an APP badge. They never appear under the name of the person who installed it.
A bot that only posts can be simpler as an incoming webhook: one secret URL for one channel, no token. Make a bot when it should post to several channels, reply in threads, or hear what people write.

Make and install a bot

1

Make an app

In your organization’s settings, open Developer, then the Apps tab, and select New app. Give it the name its posts should show, such as Deploy bot. Organization owners and admins make apps.
2

Install it in a workspace

Select the app, and under the workspace choose what it can do: Post messages (chat:write), and Hear messages (messages:read) if it should get each new message in its channels. Select Install. Workspace owners and admins install apps.
3

Copy its token

Mantle shows the bot token once. Store it right away in your bot’s secret manager, for example as MANTLE_BOT_TOKEN.
4

Add it to channels

In a channel, open the ··· menu and choose Integrations, then Add source. Under Bots, select the app. A bot can only post to, and hear, the channels it has been added to.
To add a bot to a channel, you need to be able to manage the channel: you created it, or you’re a workspace admin or owner.
If you lose the token, open the app on the Developer page and select Rotate token…. The old token stops working at once.

Check the token

GET /v1/me answers with the app, its workspace, and what it may do. Every request sends the token as a bearer token:
userId is the id the bot’s own posts carry as their author. GET /v1/channels lists the channels it has been added to, as { "data": [{ "id", "name", "archived" }] }.

Post a message

Send a POST to /v1/channels/{channelId}/messages with a JSON body that has a text field:
An Idempotency-Key header makes a retry safe: a repeat of the key answers with the first post and posts nothing new. A post that went through answers 200:
duplicate is true when the request repeated an earlier Idempotency-Key. threadRootMessageId is there when the post went into a thread. The markdown follows the incoming webhooks rules: mentions don’t notify anyone, images show as links, and HTML shows as text.

Hear messages

A bot installed with Hear messages gets a message.created event for each new message in its channels, signed so it can tell the event came from Mantle.
1

Give it an event URL

Open the app on the Developer page and, under Events, enter a public https:// URL on port 443. Select Save event URL.
2

Copy its signing secret

Mantle shows the signing secret once. Store it next to the token, for example as MANTLE_WEBHOOK_SECRET.
3

Send a test event

Select Send test event. Mantle sends an app.test event and shows how your URL answered.
Each event is a POST with a JSON body:
  • threadRootMessageId is the message whose thread this one is in, or null for a message in the channel itself.
  • author.kind is person or app. author is null once the author’s account is gone.
  • A bot never gets its own posts. It does get other apps’ posts, so a bot that answers bots should make sure two of them can’t answer each other forever.
  • Agent replies, Linear, Stripe and Notion posts, and direct messages aren’t sent. Incoming webhooks’ posts are, as an app’s.
  • The text is read when the event is sent: a message edited first is sent as it is then, and one deleted first isn’t sent.
Answer with any 2xx within 10 seconds. Do slow work after you answer.

Check the signature

Every event has these headers: Check the signature against the exact body you received, before parsing it, and refuse events more than 5 minutes old. The SDK does all of this:

A small echo bot

This bot answers “pong” in the thread of any message that says “ping”. It runs on Bun: save it as bot.js, set the two environment variables, and run bun bot.js. Then use its public https:// address as the event URL.

Retries and pausing

An event that fails is sent again after 10 seconds, 1 minute, 5 minutes, and 30 minutes. After the fifth failure, Mantle gives up on it.
  • Retried: network errors, timeouts, and the answers 408, 409, 425, 429, and any 5xx.
  • Not retried: any other 4xx, and a URL that isn’t a public https:// address.
If a bot’s events keep failing for an hour, they pause. The app’s page on the Developer page says so. Fix the receiver, send a test event, then select Resume events. Messages posted while events were paused aren’t sent.

Errors

A refused request answers with an error body:

Limits

Manage a bot

Open the app on the Developer page.
  • Rotate token… issues a new token; the old one stops working at once.
  • Change URL points its events somewhere else, with the same signing secret. Rotate signing secret… issues a new one, and Stop events… stops sending it messages.
  • Uninstall… removes it from the workspace: its token stops working and it leaves its channels. Its posts stay.
  • Delete app uninstalls it everywhere.
If the person who installed a bot leaves the workspace, the bot pauses: its requests answer 403 app_paused. A workspace admin can select Install again to give it a new token. To take a bot out of one channel, open the channel’s Integrations, select the bot, and choose Remove from channel. The person who added it, or a workspace admin, can.

Keep the token secret

Anyone with the token can post as the bot in its channels. Anyone with the signing secret could send your bot events that look like Mantle’s. Store both as secrets, never in a repository or a shared document. If either leaks, rotate it.
The token can’t read a channel’s history or anything else in your workspace; a bot hears only the events Mantle sends it.

Incoming webhooks

Post into one channel with a secret URL, no token.

Channels

Create channels and decide who can manage them.