Incoming webhooks API
Post a plain-text message into one chat with a single HTTPS request, without a bot or an Authorization header. To create a webhook and copy its URL, see Incoming webhooks.
https://donutchat.com/webhooks/v1/{webhook_id}/{token} Send requests to the URL exactly as you copied it from the app. The same path also works on https://api.donutchat.com. The token at the end of the URL is the credential, so keep the whole URL secret. See Authentication.
Request
Section titled “Request”Send a JSON body with Content-Type: application/json.
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The message text, up to 80,000 UTF-8 bytes. Can’t be empty. |
username | string | No | Sender name for this message, up to 32 bytes. Defaults to the webhook’s Display Name, or Webhook if it has none. |
avatar_url | string | No | Sender picture for this message. Use a public https image URL. Defaults to the webhook’s avatar. |
Other fields are ignored. Webhook messages are always plain text; there’s no field for Markdown.
These examples read the URL from the DONUT_WEBHOOK_URL environment variable:
curl -X POST "$DONUT_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{"content": "Build 482 passed on main.", "username": "CI"}'const res = await fetch(process.env.DONUT_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: 'Build 482 passed on main.', username: 'CI' }),});if (!res.ok) throw new Error(`Webhook failed (${res.status}): ${await res.text()}`);import jsonimport osimport urllib.request
request = urllib.request.Request( os.environ["DONUT_WEBHOOK_URL"], data=json.dumps({"content": "Build 482 passed on main.", "username": "CI"}).encode(), headers={"Content-Type": "application/json"},)urllib.request.urlopen(request) # Raises HTTPError for 4xx and 5xx responses.Response
Section titled “Response”A successful request returns HTTP 200:
{ "ok": true, "message_id": 5678, "chat_id": 1234}In JavaScript, read these IDs with a parser that keeps 64-bit integers exact. See IDs are 64-bit integers.
Errors
Section titled “Errors”Most errors return a JSON body:
{ "ok": false, "error": "rate_limit_exceeded", "message": "Too many requests. Please try again later."}| HTTP | error | Cause | What to do |
|---|---|---|---|
| 400 | invalid_json | The body isn’t valid JSON, or a field isn’t a string. | Send a JSON object with string fields. |
| 400 | missing_content | content is missing or empty. | Include the message text. |
| 400 | content_too_long | content is over 80,000 UTF-8 bytes. | Shorten or split the message. |
| 401 | unauthorized | The token is wrong, the URL was rotated, or the webhook was deleted. | Copy the current URL from the app. |
| 403 | webhook_disabled | The webhook is switched off. | Switch it back on in the app. |
| 404 | not_found | The webhook ID in the URL isn’t a number. | Use the URL exactly as copied. |
| 404 | None (plain text 404 page not found) | The URL has the wrong shape, such as a missing token, an extra path segment or a trailing slash. | Use the URL exactly as copied. |
| 405 | None (empty body) | The method isn’t POST. | Send a POST request. |
| 429 | rate_limit_exceeded | The webhook reached a rate limit. | Wait, then retry with increasing delays. |
| 500 | send_failed | username is longer than 32 bytes, or something went wrong on the server. | Shorten username. Otherwise, retry with increasing delays. |
| 503 | None (plain text server is not ready) | DonutChat is briefly unavailable, for example during maintenance. | Retry with increasing delays. |
Rate limits
Section titled “Rate limits”Each webhook accepts up to 60 requests per minute and 1,000 requests per hour.
- Both limits are rolling windows: they count the requests in the last 60 seconds and the last 60 minutes. There’s no fixed reset time.
- Requests that are rejected for invalid JSON or content still count.
- A 429 response has no
Retry-Afterheader. - You can’t change the limits. The webhook’s screen in the app shows them under Rate Limit.
Webhooks accept requests from any IP address.
How webhook messages appear
Section titled “How webhook messages appear”- The message shows the webhook’s display name and avatar, or the
usernameandavatar_urlfrom the request. The default name is “Webhook”. - Markdown shows as typed unless a reader in the iPhone, Android or desktop app chooses Formatted under Message display or selects View formatted on the message. The web app always shows it as typed.
- Members get the message like any other. Members who don’t have the app open get a push notification with the message text, unless they muted the chat. The person who created the webhook doesn’t get push notifications for its messages.
- Bots never receive webhook messages, so a webhook can’t trigger a bot.
Webhooks only post new plain-text messages. They can’t attach files, reply in threads, send Markdown or cards, react, or edit or delete their messages. To reply in threads or send Markdown and cards, use a bot: see Send messages and Thread replies and streaming. Bots can’t attach files, react, or edit or delete posted messages either; a bot can only update its own thread replies.
Related
Section titled “Related”- Incoming webhooks: create and manage webhooks in the app
- Authentication
- Send messages
- Limits