Incoming Webhooks
Incoming webhooks let an external service push a message into a specific DonutChat chat with a single HTTPS request. They’re one-way, require no bot identity, and can be enabled or revoked per chat.
At a glance
Section titled “At a glance”| Direction | External service → chat |
| Auth | Opaque token carried in the URL path |
| Transport | POST over HTTPS (application/json) |
| Typical use case | Alerts, CI/CD notifications, monitoring, cron-triggered updates |
When to use this
Section titled “When to use this”Pick an incoming webhook when you want the simplest possible “post a message into this chat” path and the sender doesn’t need a reusable identity. If you need to reply as a named bot across multiple chats, use Bot Send instead. If you also need to receive chat events, you need a bot — see Bot Receive.
For users
Section titled “For users”import { Steps } from ‘@astrojs/starlight/components’;
- Open the chat where you want the webhook to post.
- Open chat settings → Webhooks → Create webhook.
- Fill in a name and (optionally) a description, default display name, and default avatar.
- Tap Create. DonutChat shows the full webhook URL — this is the only time the token is visible.
- Copy the URL and paste it into your external system’s webhook configuration.
import { Aside } from ‘@astrojs/starlight/components’;
For developers
Section titled “For developers”Endpoint
Section titled “Endpoint”POST https://api.donutchat.com/webhooks/v1/{webhook_id}/{token}The token authenticates the request via the URL path. Do not set an Authorization header — the token lives in the URL.
Request headers
Section titled “Request headers”| Header | Value |
|---|---|
Content-Type | application/json |
Request body
Section titled “Request body”{ "content": "Build #482 passed on main.", "username": "CI Bot", "avatar_url": "https://example.com/ci-avatar.png"}| Field | Type | Required | Notes |
|---|---|---|---|
content | string | yes | Message body. Max 4000 characters. |
username | string | no | Overrides the webhook’s default display name for this message. |
avatar_url | string | no | Overrides the webhook’s default avatar URL for this message. |
Samples
Section titled “Samples”import { Tabs, TabItem } from ‘@astrojs/starlight/components’;
curl -X POST \ "https://api.donutchat.com/webhooks/v1/1234/YOUR_WEBHOOK_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "content": "Build #482 passed on main.", "username": "CI Bot" }'const url = 'https://api.donutchat.com/webhooks/v1/1234/YOUR_WEBHOOK_TOKEN';
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: 'Build #482 passed on main.', username: 'CI Bot', }),});
if (!response.ok) { const err = await response.json(); throw new Error(`${err.error}: ${err.message}`);}
const { message_id, chat_id } = await response.json();console.log('Posted message', message_id, 'to chat', chat_id);Response
Section titled “Response”Success — HTTP 200:
{ "ok": true, "message_id": 98765, "chat_id": 1234}Error — HTTP 4xx or 5xx:
{ "ok": false, "error": "<code>", "message": "<human-readable>"}Error codes
Section titled “Error codes”| HTTP status | error code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_json | Body was not valid JSON | Send a well-formed JSON body |
| 400 | missing_content | content field is empty or missing | Provide a non-empty content string |
| 400 | content_too_long | content exceeds 4000 characters | Split or shorten the message |
| 401 | unauthorized | Token is invalid, or the webhook id in the URL doesn’t exist | Confirm the full URL including token; rotate the token if compromised |
| 403 | webhook_disabled | Webhook exists but is disabled | Re-enable it in the chat’s webhook settings |
| 403 | ip_not_allowed | Client IP is not in the webhook’s IP allowlist | Add your IP in the webhook settings or remove the allowlist |
| 404 | not_found | Webhook URL is malformed (path doesn’t match /webhooks/v1/{id}/{token}) | Verify the URL shape |
| 405 | method_not_allowed | Used a method other than POST | Use POST |
| 429 | rate_limit_exceeded | Per-minute or per-hour rate limit hit | Back off; the limit resets on the configured window |
| 500 | send_failed | Internal error while delivering the message | Retry with exponential backoff |
Limits and gotchas
Section titled “Limits and gotchas”- Message size:
contentis capped at 4000 characters (same cap as bot-sent messages). - Default rate limits: 60 messages/minute and 1000 messages/hour per webhook (both configurable at creation/update time;
0disables either cap). - IP allowlist: Optional. Leave empty to accept requests from anywhere. Entries must be exact IP addresses — CIDR ranges are not currently supported and will not match any caller.
- No attachments: Incoming webhooks send text-only. File uploads require the bot send API or a different flow.
- No reactions or edits: Webhooks can only post new messages.
Related
Section titled “Related”- Bot Send — post messages with a persistent bot identity and bearer-token auth.
- Bot Receive — receive real-time chat events over WebSocket.