> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mantle.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# Incoming webhooks

> The three URLs an incoming webhook takes: Mantle JSON, Slack format and GitHub events, with every field, answer and error.

An incoming webhook posts into one channel. Its URL is its credential: there is no header to send, and anyone who has
the URL can post. For how to create one, format messages and use it from other tools, see
[Incoming webhooks](/integrations/incoming-webhooks).

## Endpoints

Every webhook takes three addresses, all `POST`:

| Address | Takes | Answers |
| - | - | - |
| `https://api.mantle.chat/api/hooks/{webhookId}/{token}` | [Mantle JSON](#mantle-json) | JSON |
| `https://api.mantle.chat/api/hooks/{webhookId}/{token}/slack` | [Slack's format](#slack-format) | Plain text, as Slack |
| `https://api.mantle.chat/api/hooks/{webhookId}/{token}/github` | [GitHub's events](#github-events) | JSON |

Copy the URL from the channel's **Integrations**: **Copy URL**, **Copy Slack URL** or **Copy GitHub URL**. It is shown
in full only when the webhook is created or its URL is rotated.

## Mantle JSON

`POST https://api.mantle.chat/api/hooks/{webhookId}/{token}`, with `content-type: application/json`.

### Body

All three fields are strings.

| Field | Required | Notes |
| - | - | - |
| `text` | Yes | Markdown, 1 to 40,000 characters after trimming |
| `thread_key` | No | 1 to 200 characters. Posts with the same key reply in one thread; the first starts it |
| `idempotency_key` | No | 1 to 200 characters. A repeat answers with the first post, and posts nothing new |

The body can be up to 64 KB. Fields Mantle doesn't know are ignored.

```bash theme={null}
curl -X POST "https://api.mantle.chat/api/hooks/$WEBHOOK_ID/$WEBHOOK_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"text":"Build 1042: tests passed","thread_key":"build-1042","idempotency_key":"build-1042-tests"}'
```

### Answer

`200`, when the post went through or the request repeated an `idempotency_key`:

```json theme={null}
{
  "ok": true,
  "message_id": "6f0c2b8e-…",
  "duplicate": false,
  "thread_root_message_id": "1d4e9a77-…"
}
```

| Field | Notes |
| - | - |
| `message_id` | The post. For a repeated `idempotency_key`, the first post. |
| `duplicate` | `true` when the request repeated an earlier `idempotency_key`. |
| `thread_root_message_id` | Only with a `thread_key`: the message the thread hangs from, which may be this post. |

### Errors

Every error answers `{ "ok": false, "error": "…", "message": "…" }`.

| Status | `error` | When |
| - | - | - |
| `400` | `invalid_payload` | The body isn't JSON, `text` is missing, or `idempotency_key` is empty or too long |
| `400` | `empty_text` | `text` is empty or only spaces |
| `400` | `text_too_long` | `text` is over 40,000 characters |
| `400` | `invalid_thread_key` | `thread_key` is empty or over 200 characters |
| `403` | `webhook_paused` | The webhook is paused, because the member who created it left the workspace |
| `404` | `webhook_not_found` | The URL is wrong, was rotated, or its webhook was deleted |
| `409` | `channel_archived` | The webhook's channel is archived |
| `413` | `payload_too_large` | The body is over 64 KB |
| `429` | `rate_limited` | Too many posts in the last minute; `Retry-After` says how many seconds to wait |
| `503` | `rate_limiter_unavailable` | Mantle couldn't check the rate limit |
| `500` | `post_failed` | Mantle couldn't save the post |

Retry only `429`, `500` and `503`, with the same `idempotency_key`. A wrong URL and a deleted webhook answer the same
`404`.

## Slack format

`POST https://api.mantle.chat/api/hooks/{webhookId}/{token}/slack`, with Slack's incoming webhook body as JSON, or as a
form with the JSON in a `payload` field (`application/x-www-form-urlencoded`).

* **Read:** `text`, `blocks` and `attachments`. When there are blocks, they're the post, and `text` is only Slack's
  notification text.
* **Ignored:** `username`, `icon_url`, `icon_emoji` and `channel`. The webhook keeps its own name, icon and channel.
* **Body:** up to 64 KB.

It answers as Slack does, in plain text:

| Status | Answer | When |
| - | - | - |
| `200` | `ok` | The post went through |
| `400` | `invalid_payload` | The body isn't Slack's format, or its text is over 40,000 characters |
| `400` | `no_text` | Nothing in it would show |
| `403` | `action_prohibited` | The webhook is paused |
| `404` | `no_service` | The URL is wrong, was rotated, or its webhook was deleted |
| `410` | `channel_is_archived` | The webhook's channel is archived |
| `413` | `payload_too_large` | The body is over 64 KB |
| `429` | `rate_limited` | Too many posts in the last minute; `Retry-After` says how many seconds to wait |
| `503` | `rate_limiter_unavailable` | Mantle couldn't check the rate limit |
| `500` | `post_failed` | Mantle couldn't save the post |

The Slack URL takes no `thread_key` or `idempotency_key`.

## GitHub events

`POST https://api.mantle.chat/api/hooks/{webhookId}/{token}/github`, as GitHub sends a repository's or an organization's
webhook events, as JSON or as a form.

| Header | Required | Notes |
| - | - | - |
| `X-GitHub-Event` | Yes | The event's name, such as `push` or `pull_request`. Without it, the answer is `400`. |
| `X-GitHub-Delivery` | No | GitHub's delivery id, used as the idempotency key, so a redelivered event never posts twice. |

* **Events that post:** `push`, `pull_request`, `pull_request_review`, `issues`, `issue_comment`, `release` and
  `workflow_run`, plus `ping` when the webhook is added. See
  [Use it with GitHub](/integrations/incoming-webhooks#use-it-with-github) for which actions of each post.
* **Threads:** a pull request's events, reviews and comments share a thread, and so do an issue's.
* **Body:** up to 1 MB.

### Answer

* **An event that posts** answers as [Mantle JSON](#answer) does.
* **An event that doesn't post** answers `200 { "ok": true, "posted": false }`, after its URL is checked, so GitHub's
  delivery log shows it as delivered.
* **Errors** answer as [Mantle JSON's](#errors) do. A body that isn't a GitHub event, or a request without
  `X-GitHub-Event`, answers `400 invalid_payload`, and a body over 1 MB answers `413 payload_too_large`.

## Limits

| Limit | Value |
| - | - |
| Posts per webhook | 30 a minute |
| Posts per workspace, in total | 300 a minute |
| Request body | 64 KB, or 1 MB for GitHub's events |
| Message text | 40,000 characters |
| `thread_key`, `idempotency_key` | 200 characters each |
| Webhooks per workspace | 25 |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.