# Incoming webhooks

> Post to a channel with one secret URL and one JSON body.

The simplest way for software to post: a secret URL that accepts one JSON body. No token, no code to
write. Any app with the `incoming-webhook` permission can have as many as it needs, each posting to one
channel. Create them under _Manage → Installed apps → the app → Incoming webhooks_; the URL is shown
once.

**curl**

```sh
curl -X POST "https://app.example.com/api/public/apps/hooks/<secret>" \
  -H 'Content-Type: application/json' \
  -d '{"text": "Build passed"}'
```

**JavaScript**

```js
const response = await fetch("https://app.example.com/api/public/apps/hooks/<secret>", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text: "Build passed" }),
});
// { ok: true, id: "<message id>" }
console.log(await response.json());
```

**Python**

```python

request = urllib.request.Request(
    "https://app.example.com/api/public/apps/hooks/<secret>",
    data=json.dumps({"text": "Build passed"}).encode(),
    headers={
        "Content-Type": "application/json",
        # Name your app: some proxies refuse urllib's default User-Agent (Cloudflare answers 1010).
        "User-Agent": "my-app/1.0",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(json.load(response))  # {"ok": true, "id": "<message id>"}
```

Success is `{ "ok": true, "id": "<message id>" }`. The message appears from the app, in the channel the
webhook was made for. The URL is the credential: it cannot call the Web API, and a token cannot post
through it. Replacing the URL stops the old one working.

## The body

The same as Slack's, so tools that already notify Slack can point at an Amedeus URL:

- `{"text": "…"}`, Markdown, up to 8000 characters.
- Or Slack's `attachments`: without `text`, each attachment's `pretext`, `title`, `text` and `fields`
(one `title: value` line per field) become the message. Slack's `<url|label>` links become Markdown
links.

A body with nothing to post is `400 invalid_arguments`. Other fields (`channel`, `username`, colours,
blocks) are ignored: the channel and the author are fixed by the URL.

Limit: 60 messages a minute per URL.

