Incoming webhooks
An incoming webhook is a secret URL that posts messages into one text channel of a nest. Anything that can send an HTTP request can use it: CI pipelines, uptime monitors, cron jobs, home automation.
Security model
Nest channels are end-to-end encrypted: members encrypt on their own devices, and the server only ever stores ciphertext. A webhook can't work that way, because the sender is a plain HTTP client with no keys. So webhook posts are a deliberate, visible exception:
- The server receives webhook text in plain form, encrypts it at rest with the server's model key (the same storage used for AI replies), and serves it to channel members.
- Every webhook post carries a WEBHOOK badge in the client, which says the post is not end-to-end encrypted.
- Don't send anything through a webhook that you wouldn't want the server operator to be able to read.
The server only stores a SHA-256 hash of each webhook token. The full URL is shown once, when the webhook is created or its URL is reset. If you lose it, reset it.
Creating a webhook
You need the Manage server permission (or Administrator, or nest ownership).
-
Open Nest settings → Apps → Integrations.
-
Enter a name, pick a text channel, and click New webhook.
-
Copy the URL right away. It looks like:
https://<api-host>/webhooks/<webhook-id>/<token>
From the same screen you can rename a webhook, move it to another text channel, reset its URL (the old URL stops working right away), or delete it. Messages a webhook already posted stay in the channel after it's deleted. Deleting the channel or the nest deletes its webhooks.
A nest can have up to 15 webhooks.
Posting a message
POST /webhooks/{id}/{token}
Content-Type: application/json
| Field | Type | Notes |
|---|---|---|
content | string | The message text. Up to 2000 characters once composed. |
text | string | Used when content is missing, so Slack-style senders work. |
username | string | Optional. Overrides the display name for this one post (1–80 characters). Invalid values fall back to the webhook's name. |
embeds | array | Optional. Up to 10 objects with title, description, and url. Each embed becomes a block of lines under the content, so Discord-style senders still show something. |
At least one of content, text, or an embed with text is required.
Example
curl -X POST "https://<api-host>/webhooks/<webhook-id>/<token>" \
-H "Content-Type: application/json" \
-d '{"content": "Deploy finished", "username": "CI"}'
{ "id": "6f3c…", "channel_id": "a91e…", "created_at": 1790812800000 }
GitHub Actions:
- name: Tell the nest
run: |
curl -fsS -X POST "$ALEJA_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"content\": \"${{ github.repository }} deployed ${{ github.sha }}\"}"
env:
ALEJA_WEBHOOK: ${{ secrets.ALEJA_WEBHOOK }}
Responses
| Status | Meaning |
|---|---|
200 | Posted. The body has the new message's id, channel_id, and created_at. |
400 | The body isn't valid JSON, the content is empty, or it's over 2000 characters. |
415 | The Content-Type header isn't application/json. |
422 | The JSON has the wrong shape, for example content is a number. |
404 | Unknown webhook, wrong token, or the webhook's channel no longer exists. A wrong token and a missing webhook look the same on purpose. |
413 | The request body is over 32 KB. |
429 | Rate limited. Each webhook gets a burst of 5 messages, refilling over 3 seconds. The Retry-After header says how long to wait. |
403 | The calling network is IP-banned by a server admin. |
Errors return { "error": "<message>" }.
How posts appear
- The author is the webhook's name, or the
usernameoverride, with a generated avatar and a WEBHOOK badge. - Webhook posts can't be edited. Members with Manage messages can delete them. Everyone can react to them, reply to them, and pin them where pinning is allowed.
- Posts aren't tied to a member's profile, so there's no profile card, mention, block, or report action on the author.
Management API
All of these need a session token (Authorization: Bearer <token>) for a member with Manage server.
| Method and path | Body | Result |
|---|---|---|
GET /v1/servers/{server_id}/webhooks | { webhooks: [Webhook] } | |
POST /v1/servers/{server_id}/webhooks | { name, channel_id } | Webhook plus token and path (shown once) |
PATCH /v1/webhooks/{id} | { name?, channel_id? } | Webhook |
POST /v1/webhooks/{id}/token | { id, token, path }. The old token stops working. | |
DELETE /v1/webhooks/{id} | { ok: true } |
A Webhook is
{ id, server_id, channel_id, channel_name, name, created_by, created_by_name, created_at, last_used_at }.
channel_name is null if the channel was removed. Creating, deleting, and resetting webhooks is
recorded in the server's audit log.
