mirror of
https://github.com/open-webui/docs.git
synced 2026-11-04 08:08:30 +00:00
Document Apps for channels
Admins can now connect external apps that answer mentions and button clicks in channels. A new page covers adding and using apps, installing and configuring them, private network setups and troubleshooting, and a second page explains how to build one with the oix SDK. The env reference, database schema, hardening guide, security policy and Channels page now point to them.
This commit is contained in:
1 parent
94cf63691a
commit
7159347dbd
10 files changed
+506
-8
No files matched your search
@@ -102,7 +102,7 @@ The single **Notification Webhook** field under **Settings > Account** has been
|
||||
|
||||
## 3. Channel Webhooks: External Message Integration
|
||||
|
||||
Channel Webhooks allow external services, automation tools, or scripts to post messages directly into Open WebUI channels. This enables seamless integration with monitoring systems, CI/CD pipelines, notification services, or any custom automation.
|
||||
Channel Webhooks allow external services, automation tools, or scripts to post messages directly into Open WebUI channels. This enables seamless integration with monitoring systems, CI/CD pipelines, notification services, or any custom automation. A webhook posts messages into one channel. A service that should also answer `@mentions` or post buttons is built as an [App](/features/channels/apps).
|
||||
|
||||
### Use Cases
|
||||
|
||||
@@ -123,7 +123,7 @@ Messages posted via webhooks appear in the channel with the webhook's identity,
|
||||
|
||||
### Managing Channel Webhooks
|
||||
|
||||
Only **channel managers** and **administrators** can create and manage webhooks for a channel.
|
||||
Only the channel's creator and **administrators** can create and manage webhooks for a channel.
|
||||
|
||||
#### Creating a Webhook
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: "Apps"
|
||||
---
|
||||
|
||||
# Apps
|
||||
|
||||
Apps are external services that join [channels](/features/channels) as members. Mention an app and it answers, either privately to you or in the channel for everyone. Apps can post messages with buttons, update their own messages and, when an admin allows it, follow every message in the channels they belong to.
|
||||
|
||||
An app runs on its own server. Open WebUI opens the connection to the app, so Open WebUI itself can stay on a private network with no public callback address. To post messages into a channel without answering anyone, a [channel webhook](/features/administration/webhooks#3-channel-webhooks-external-message-integration) is simpler.
|
||||
|
||||
:::info
|
||||
Apps were added in v0.12.1.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Using Apps in a Channel
|
||||
|
||||
### Add an app to a channel
|
||||
|
||||
1. Open the channel and click the member count next to the people icon in the header.
|
||||
2. Click **Add Member**.
|
||||
3. Select the app under **Apps** and click **Add**.
|
||||
|
||||
The app now appears in the member list with an **App** badge. To take it out again, use the remove button next to it in the same list.
|
||||
|
||||
Only the channel's creator and admins can add and remove apps. To add an app you also need access to it, and the app must be enabled. You only see apps you are allowed to use. Apps join standard and group channels; direct messages stay between people.
|
||||
|
||||
An app granted **Read messages and history in joined channels** receives every message posted in the channel and can read its history. Ask an admin which scopes an app has before adding it to a channel with sensitive content.
|
||||
|
||||
### Mention an app
|
||||
|
||||
Type `@` in a channel where the app is a member and pick it from the **Apps** group in the suggestion list. Send your message and the app receives it. One message reaches at most the first 10 apps it mentions.
|
||||
|
||||
### Private and channel replies
|
||||
|
||||
An app chooses where its answer goes:
|
||||
|
||||
- **Private replies** appear under the message that triggered them (your message for a mention, the app's message for a button click), marked **Only visible to you**, with a **Dismiss** link. Only you see them, and only until you dismiss them, reload the page or switch to another channel or thread. A private reply appears only if you are still in that channel or thread when it arrives. Errors and file downloads are only ever shown to you.
|
||||
- **Channel replies** are posted as normal channel messages under the app's name, with an **App** label. Everyone in the channel sees them and they stay in the history.
|
||||
|
||||
An app can also send a Markdown file as a private reply. It shows as a **Download** button with the file name, for example **Download report.md**, that saves the file to your device.
|
||||
|
||||
If the app cannot answer, an error appears under your message. Ask an admin to check the app in **Settings > Admin > Apps**. [Troubleshooting](#troubleshooting) lists the messages you are most likely to see.
|
||||
|
||||
### Buttons
|
||||
|
||||
Channel messages from an app can carry buttons and cards. A card shows a title, text, fields and a small image, with a colored bar on the side. A link button opens a web page in a new tab. Any other button sends the click to the app, which can answer privately, post a new message or update the message the button belongs to.
|
||||
|
||||
---
|
||||
|
||||
## Setting Up Apps
|
||||
|
||||
### Enable apps
|
||||
|
||||
1. Go to **Settings > Admin > General**.
|
||||
2. Turn on **Apps** and **Channels**, then save. Apps only act in channels, so both are needed.
|
||||
3. The **Apps** tab appears in the admin settings.
|
||||
|
||||
The same switch is available as [`ENABLE_APPS`](/reference/env-configuration#enable_apps). Turning it off closes every app connection.
|
||||
|
||||
### Install an app
|
||||
|
||||
You need two things from the app's operator: its **App URL**, for example `https://app.example.com/events`, and its **API key**.
|
||||
|
||||
For an app on your own network, on the Docker host or on plain HTTP, set up [Private and plain HTTP app servers](#private-and-plain-http-app-servers) first.
|
||||
|
||||
1. Go to **Settings > Admin > Apps** and click **Add app** (the plus button).
|
||||
2. Enter the **App URL** and **API Key**, then click **Continue**. Open WebUI loads the app's details from that URL.
|
||||
3. Review the scopes the app requests. Switch off any you do not want to grant, then click **Continue**.
|
||||
4. Check the **Name**, the **App handle** and **Access**. The handle is a short unique ID shown next to the name in the **Apps** list. It takes lowercase letters, digits and hyphens, starts with a letter and has at most 40 characters. The handle and the App URL are fixed once the app is saved; to move an app to a new address, install it again under a new handle.
|
||||
5. Click **Connect**.
|
||||
|
||||
If **Connect** fails, the app is still saved, disabled, with its handle. Fix the cause and click **Connect** again.
|
||||
|
||||
If the app does not publish its details, fill in **App URL** and **API Key**, then click **Enter details manually**. Enter the name, the app's Ed25519 public key and the scopes yourself. Set **App handle** to exactly the app's ID (`manifest_id` in oix): for a manually entered app Open WebUI uses the handle as that ID, and the connection fails if the two differ. This needs an app ID that is also a valid handle. An app with any other ID has to publish its details.
|
||||
|
||||
The App URL cannot contain credentials, a query (`?`) or a fragment (`#`), and it must answer directly without a redirect. Open WebUI connects to apps directly and ignores `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`, so the Open WebUI server needs a direct network path to every app.
|
||||
|
||||
The API key and the app's connection credential are stored encrypted with a key derived from [`WEBUI_SECRET_KEY`](/reference/env-configuration#webui_secret_key). Keep that value stable and the same on every Open WebUI instance; if it changes, follow [Apps stop connecting after WEBUI_SECRET_KEY changed](#apps-stop-connecting-after-webui_secret_key-changed).
|
||||
|
||||
### Private and plain HTTP app servers
|
||||
|
||||
By default every app URL must use HTTPS, and every address its hostname resolves to must be public. To allow an app on localhost, a private network or plain HTTP, list its origin in [`APP_ALLOWED_PRIVATE_ORIGINS`](/reference/env-configuration#app_allowed_private_origins) on the Open WebUI server and restart:
|
||||
|
||||
```bash
|
||||
APP_ALLOWED_PRIVATE_ORIGINS=http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
The match is exact on scheme, host and port. A listed `http://` origin may use plain HTTP; a listed origin of either scheme may resolve to private, loopback or reserved addresses. Write every entry with its scheme, for example `http://host.docker.internal:8000`. An entry without a scheme, or with credentials or a fragment, breaks every app, public ones included: loading app details fails with `Invalid app URL` and installed apps stay on **Connecting**.
|
||||
|
||||
Inside Docker, `127.0.0.1` is the Open WebUI container itself. For an app running on the Docker host, use `http://host.docker.internal:8000` as the origin (on Linux, start the container with `--add-host=host.docker.internal:host-gateway`). For an app in another container on the same Docker network, use its service or container name, for example `http://hello-app:8000`. For an app on another machine in your network, use its LAN address, for example `http://192.168.1.50:8000`. The App URL then starts with exactly that origin, for example `http://192.168.1.50:8000/events`.
|
||||
|
||||
App connections always verify the app's certificate. For an app on your network with a self-signed certificate, list its `http://` origin and use plain HTTP, or give the app a certificate the Open WebUI server trusts.
|
||||
|
||||
### Scopes
|
||||
|
||||
Scopes limit what an app can do. You pick which scopes to grant when you install the app, and you can change the selection later in **Configure** with **Edit** next to **Scopes**.
|
||||
|
||||
| Scope | Shown as | What it allows |
|
||||
|-------|----------|----------------|
|
||||
| `channels:mentions:read` | Read app mentions in joined channels | Receive messages that mention the app |
|
||||
| `channels:messages:read` | Read messages and history in joined channels | Receive mentions and every new, edited and deleted message in joined channels. Read message history and threads |
|
||||
| `channels:messages:write` | Send messages and edit own messages in joined channels | Post channel messages and edit the app's own messages |
|
||||
| `channels:read` | Read joined channel details | List the channels the app was added to, with name, type and whether they are private |
|
||||
|
||||
Answering mentions needs `channels:mentions:read` or `channels:messages:read`. Button clicks reach the app with any scopes. Channel replies and button updates need `channels:messages:write`; private replies need no further scope. Every scope applies only to channels the app was added to.
|
||||
|
||||
### Access
|
||||
|
||||
**Access** sets who can use the app: mention it, click its buttons and add it to channels. Using it in a channel also needs read access to that channel.
|
||||
|
||||
- **Everyone** (the default for a new app): every user.
|
||||
- **Restricted**: admins plus the users and [groups](/features/authentication-access/rbac/groups) you add. With nobody added, only admins can use the app.
|
||||
|
||||
### Connection status
|
||||
|
||||
Open WebUI keeps a live connection to each enabled app and reconnects on its own, waiting up to 30 seconds between attempts. Every Open WebUI worker opens its own connection; see [Scaling Open WebUI](/getting-started/advanced-topics/scaling) for workers and Redis. The **Apps** tab shows the state of each enabled app and refreshes every few seconds:
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| **Connected** | A verified connection is open. With several workers sharing Redis, at least one of them is connected. |
|
||||
| **Connecting** | Open WebUI is trying to reach the app and will keep retrying. |
|
||||
| **Disconnected** | No connection is open. |
|
||||
|
||||
Disabled apps are dimmed. Hover the switch to see **Enabled**, **Disabled** or **Uninstalled**.
|
||||
|
||||
When an app stays on **Connecting** or **Disconnected**, turn its switch off and on again, then click **Connect** in the dialog that opens. The dialog shows why the connection fails; [Troubleshooting](#troubleshooting) explains each message.
|
||||
|
||||
### Update, disable and uninstall
|
||||
|
||||
- **Configure** (gear icon) opens the app's settings. **Update app** (the refresh button next to the URL) loads the app's details again, for example after the operator added a scope or changed the profile image. Saving approves the updated details and selected scopes and reconnects the app. When the public key or the API key changes, saving connects the app again from scratch, as on first install.
|
||||
- The switch disables an enabled app at once. Turning it back on opens the settings, where **Connect** connects it again.
|
||||
- **Uninstall**, at the bottom of the **Configure** dialog, removes the API key, the connection credential and the app's channel memberships. Messages the app already posted stay in the channels. The app stays in the list as **Uninstalled**, with its handle, scopes and access. To use it again, turn its switch on, enter the API key, click **Connect** and add it to its channels again.
|
||||
|
||||
Rolling the database back past the Apps migration (`c92da83b017f`) is refused while any message posted by an app exists; see [Downgrade (Rollback)](/troubleshooting/manual-database-migration#downgrade-rollback).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### In the app settings
|
||||
|
||||
These messages appear in the app settings dialog while you install, update or connect an app.
|
||||
|
||||
| Message | What to check |
|
||||
|---------|---------------|
|
||||
| `App endpoints require HTTPS. For local HTTP, configure APP_ALLOWED_PRIVATE_ORIGINS.` | Use an HTTPS URL, or list the origin in `APP_ALLOWED_PRIVATE_ORIGINS`. |
|
||||
| `App resolves to a private or reserved address. For local development, configure APP_ALLOWED_PRIVATE_ORIGINS.` | The hostname points to a private address. List the origin in `APP_ALLOWED_PRIVATE_ORIGINS`. |
|
||||
| `Invalid app URL. Use an HTTP(S) URL without credentials or a fragment.` | Remove credentials and `#fragment` from the URL. If every app URL fails this way, an `APP_ALLOWED_PRIVATE_ORIGINS` entry is missing its scheme. |
|
||||
| `Invalid app URL` | Shown at **Connect** after entering details manually. The URL uses plain HTTP without an `APP_ALLOWED_PRIVATE_ORIGINS` entry, or contains credentials, a query or a fragment. |
|
||||
| `App returned an invalid response. Expected JSON.` | The App URL contains a query (`?...`), or the app answered with something other than JSON. Remove the query, or check what the App URL returns. |
|
||||
| `App response exceeds the allowed size.` | The app's details are larger than 256 KiB. |
|
||||
| `App rejected the API key. Check the API key configured on the app server.` | The key does not match the app's key. |
|
||||
| `App manifest endpoint returned HTTP <code>. Check the URL or enter details manually.` | Wrong URL, or the app redirects. Use the final URL, or enter details manually. |
|
||||
| `App returned an invalid manifest. Check its app details or enter them manually.` | The app's details are malformed (see [Manifest](/features/channels/building-apps#manifest)). |
|
||||
| `App request timed out. Check that the app server is running and reachable.` | The app did not answer within 10 seconds while its details were loaded. |
|
||||
| `App TLS verification failed. Check its certificate and hostname.` | The app's certificate is invalid or does not match its hostname. |
|
||||
| `App hostname could not be resolved. Check DNS from the WebUI server.` | DNS lookup failed on the Open WebUI server. |
|
||||
| `Could not reach the app server. Check its address, port and network access.` | Network path, firewall or port. |
|
||||
| `App connection failed. Check the API key, pinned public key and Socket.IO endpoint.` | Open WebUI could not complete the connection: the app rejected the API key, the Socket.IO path is wrong, a proxy blocks WebSockets, the certificate, DNS or network failed, the app took longer than 20 seconds or its proof did not verify against the stored public key. For an app entered manually, the handle may differ from the app's ID, or the address is private and not listed in `APP_ALLOWED_PRIVATE_ORIGINS`. Click **Update app** if the app's key changed. If `WEBUI_SECRET_KEY` changed, see [Apps stop connecting after WEBUI_SECRET_KEY changed](#apps-stop-connecting-after-webui_secret_key-changed). |
|
||||
| `App rejected its connection credential. Review credentials before reconnecting.` | The app no longer accepts the credential it issued at the last **Connect**, usually because its signing key was replaced. In **Configure**, click **Update app** and save; with the new public key Open WebUI connects from scratch and the app keeps its channels. If the app's API key changed, enter the new one and click **Connect**. If neither changed, uninstall the app, connect it again with its API key and add it back to its channels. |
|
||||
| `App could not complete its bidirectional connection check` | The app did not acknowledge the final connection step correctly (see [Connection](/features/channels/building-apps#connection)). |
|
||||
| `App slug is already registered` | Another app, possibly an uninstalled one, uses this handle. Pick another handle, or turn the uninstalled app back on. |
|
||||
| `App identity cannot change` | The app's ID changed. Install it as a new app. |
|
||||
|
||||
The TLS, DNS and reachability messages come from loading the app's details. On **Connect**, the same problems show as `App connection failed`.
|
||||
|
||||
### In a channel
|
||||
|
||||
These messages appear under a message in the channel.
|
||||
|
||||
| Message | What to check |
|
||||
|---------|---------------|
|
||||
| `App could not respond. Check access and connection status before retrying.` | Shown after a mention. The app is disconnected, did not answer in time, returned an error or an invalid reply, or lacks a read scope. Or you or the app lost access to the channel. |
|
||||
| `App is disconnected; event was not sent` | Shown after a button click. Check the status in the **Apps** tab. |
|
||||
| `App response timed out; execution outcome may be unknown` | The app did not answer the button click within 3 seconds. It may still have acted. |
|
||||
| `App invocation denied` | You are not allowed to use this app. Check its **Access**. |
|
||||
| `Invite the app into this channel first` | The app is not a member of the channel. |
|
||||
| `App rejected event authorization. Check the app server logs for the verification failure.` | Shown after a button click. The app could not confirm the click with Open WebUI. The app's log names the reason. |
|
||||
| `App returned HTTP <code>. Check the app server logs.` | The app answered the click with an error status, for example after its handler crashed. |
|
||||
| `App processing timed out; execution outcome may be unknown.` | The app's handler ran out of time. It may still have acted. |
|
||||
| `App is busy or shutting down. The event was not accepted.` | The app is at capacity or restarting. Click again later. |
|
||||
| `App returned an invalid response` | The app's reply has unknown fields or breaks a limit. |
|
||||
| `App scope was not granted` | The app tried to post or update a channel message without `channels:messages:write`. Check its scopes. |
|
||||
|
||||
### Apps stop connecting after WEBUI_SECRET_KEY changed
|
||||
|
||||
Open WebUI encrypts each app's API key and connection credential with a key derived from `WEBUI_SECRET_KEY`. After that value changes, Open WebUI can no longer read them: every app stays on **Connecting**, and entering the API key again in **Configure** fails too. To recover, for each app:
|
||||
|
||||
1. Open **Configure** and click **Uninstall**. This clears the old credentials.
|
||||
2. Turn the app's switch on, enter its API key and click **Connect**.
|
||||
3. Add the app back to its channels.
|
||||
|
||||
---
|
||||
|
||||
## Building an App
|
||||
|
||||
An app is a server that Open WebUI connects to. Build one with [oix](https://github.com/open-webui/oix#readme), the Python SDK, or implement the protocol described in [Building Apps](/features/channels/building-apps).
|
||||
@@ -0,0 +1,244 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: "Building Apps"
|
||||
---
|
||||
|
||||
# Building Apps
|
||||
|
||||
An app is a server that Open WebUI connects to. Admins install it; admins and the channel's creator add it to channels, as described in [Apps](/features/channels/apps).
|
||||
|
||||
## Quick start with oix
|
||||
|
||||
The quickest way to build an app is [oix](https://github.com/open-webui/oix#readme), the Python SDK (Python 3.11 or later). It implements the protocol on this page, so your code only handles events:
|
||||
|
||||
```bash
|
||||
python -m pip install oix
|
||||
```
|
||||
|
||||
Embeds and link buttons need oix 0.7.0 or newer.
|
||||
|
||||
```python
|
||||
from oix import App
|
||||
|
||||
app = App(
|
||||
manifest_id="hello",
|
||||
name="Hello",
|
||||
scopes=["channels:messages:read", "channels:messages:write"],
|
||||
)
|
||||
|
||||
|
||||
@app.on("channel.message.created")
|
||||
async def hello(event):
|
||||
if event.content == "hello":
|
||||
await event.reply("Hello!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
```
|
||||
|
||||
Save it as `app.py`. The app reads its API key from `API_KEY` and its private key from the file named in `APP_PRIVATE_KEY_FILE`; the oix README has a script that writes both to `.env` once. Start the app with `. ./.env && python app.py`. It listens on port `8000` at `/events`. Put it behind HTTPS with WebSocket upgrades enabled, and its App URL is for example `https://hello.example.com/events`. [Install it](/features/channels/apps#install-an-app) with that URL and the API key, add it to a channel and send `hello`.
|
||||
|
||||
To test against a local Open WebUI, follow the oix [local development](https://github.com/open-webui/oix#local-development) steps together with [Private and plain HTTP app servers](/features/channels/apps#private-and-plain-http-app-servers).
|
||||
|
||||
An app in another language implements the protocol below directly.
|
||||
|
||||
## What an app provides
|
||||
|
||||
1. An optional **manifest** at the App URL.
|
||||
2. A **Socket.IO server** under the App URL's path, with the `/apps` namespace.
|
||||
3. An **Ed25519 key pair**, for example `openssl genpkey -algorithm ED25519 > app-private-key.pem`, with the public key from `openssl pkey -in app-private-key.pem -pubout`. The public key goes in the manifest; the private key signs the connection proof.
|
||||
4. An **API key**: a random secret you create, for example with `openssl rand -hex 32`, and give to the admin. Open WebUI sends it as `Authorization: Bearer <API key>` on the manifest request and on the Socket.IO handshake. Reject requests without it.
|
||||
|
||||
## Manifest
|
||||
|
||||
Open WebUI sends `GET <App URL>` with the API key. Answer with HTTP 200 and a JSON object of at most 256 KiB, within 10 seconds:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "hello",
|
||||
"name": "Hello",
|
||||
"description": "Says hello.",
|
||||
"profile_image_url": "https://example.com/hello.png",
|
||||
"public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
|
||||
"requested_scopes": ["channels:mentions:read", "channels:messages:write"]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Rules |
|
||||
|-------|-------|
|
||||
| `id` | Required, 1 to 100 characters. Identifies the app and must stay the same for an installed app. |
|
||||
| `name` | Required, 1 to 100 characters. |
|
||||
| `description` | Optional, up to 2,000 characters. |
|
||||
| `profile_image_url` | Optional. An `http(s)` URL of up to 2,048 characters without credentials, or a base64 `data:image/png`, `jpeg`, `gif` or `webp` URL. Up to 192 KiB in total. Users' browsers load it directly. |
|
||||
| `public_key` | Required. An Ed25519 public key in PEM format (`-----BEGIN PUBLIC KEY-----`). Open WebUI pins it at install. |
|
||||
| `requested_scopes` | Required. A list of the [scopes](/features/channels/apps#scopes). |
|
||||
|
||||
Unknown fields make the manifest invalid.
|
||||
|
||||
## Connection
|
||||
|
||||
For an App URL of `https://app.example.com/events`, Open WebUI connects as a Socket.IO client to `https://app.example.com` with the Socket.IO path `/events/socket.io`, WebSocket transport only, namespace `/apps` and the `Authorization: Bearer <API key>` header. If the app sits behind a reverse proxy, the proxy must pass WebSocket upgrades on the Socket.IO path. The handshake then runs over Socket.IO events with acknowledgements:
|
||||
|
||||
1. Open WebUI emits `app:connect` with:
|
||||
|
||||
```json
|
||||
{ "app_id": "<id Open WebUI assigned>", "challenge": "<random UUID>", "connection_token": null }
|
||||
```
|
||||
|
||||
`connection_token` is `null` when Open WebUI holds no token: on the first connect, after the admin enters a new API key, after the app's public key changes and after an uninstall. Otherwise it is the token the app returned at the last **Connect** an admin clicked.
|
||||
|
||||
2. The app acknowledges with:
|
||||
|
||||
```json
|
||||
{ "status": 200, "data": { "installation_id": "<UUID>", "connection_token": "<string>", "proof": "<JWT>" } }
|
||||
```
|
||||
|
||||
The app chooses `installation_id` (a UUID that stays the same for this installation) and `connection_token` (up to 8,192 characters). Open WebUI stores the token encrypted when an admin clicks **Connect** and sends it on every reconnect. On automatic reconnects it keeps the stored token and ignores the one the app returns, so the app must keep accepting the token it issued.
|
||||
|
||||
The **app** signs `proof` with its **Ed25519 private key** (JWT algorithm `EdDSA`, header `typ` `oix-connect+jwt`). Open WebUI verifies it with the pinned public key from the manifest. Required claims:
|
||||
|
||||
| Claim | Value |
|
||||
|-------|-------|
|
||||
| `aud` | `oix:connect` |
|
||||
| `sub` | the `installation_id` |
|
||||
| `app_id` | the `app_id` from `app:connect` |
|
||||
| `manifest_id` | the manifest `id` |
|
||||
| `challenge` | the `challenge` from `app:connect` |
|
||||
| `token_hash` | the SHA-256 hex digest of the `connection_token` in this acknowledgement |
|
||||
| `iat`, `exp` | integers, `exp` at most 60 seconds after `iat` |
|
||||
|
||||
While Open WebUI holds a token, the `installation_id` must stay the same. When it sends `null`, the app may answer with a new `installation_id`.
|
||||
|
||||
3. Open WebUI emits `app:ready` with `{}`. The app must acknowledge with exactly `{"status": 200, "data": {"received": true}}`.
|
||||
|
||||
The connection is then live. Open WebUI authenticates itself with the API key; the app authenticates itself with the signed proof. Each Open WebUI worker opens its own connection, so the app must accept several connections for the same installation.
|
||||
|
||||
The whole connection must complete within 20 seconds, and each acknowledgement within 10. Until the app acknowledges `app:ready`, every `app:request` except `ping` fails with `App connection is not ready`.
|
||||
|
||||
## Events
|
||||
|
||||
Open WebUI emits `app:event` and waits for the acknowledgement. Every event carries `type`, `app_id` (Open WebUI's id for the app) and `installation_id`.
|
||||
|
||||
**Interactions** go to `channel.mention` and `channel.action`. Answer within **3 seconds**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "<operation id>",
|
||||
"type": "channel.mention",
|
||||
"actor": { "id": "<user id>", "name": "Ada", "is_admin": false },
|
||||
"channel": { "id": "<channel id>" },
|
||||
"data": { "text": "<@A:<app_id>|Hello> hi", "message_id": "<id>", "parent_id": null },
|
||||
"execution_token": "<token>",
|
||||
"source": null,
|
||||
"app_id": "<app id>",
|
||||
"installation_id": "<installation id>"
|
||||
}
|
||||
```
|
||||
|
||||
- `channel.mention`: a user mentioned the app. `data.text` is the raw message, where the mention appears as `<@A:<app_id>|<name>>`. `data.parent_id` is the thread root when the mention was posted in a thread. Requires `channels:mentions:read` or `channels:messages:read`.
|
||||
- `channel.action`: a user clicked a button. `data` holds `message_id`, `etag`, `parent_id`, `action_id` and the button's `context`; `source` holds the message's `id` and `updated_at`.
|
||||
|
||||
**Message events** go to apps with `channels:messages:read`, for every message in the channels they joined, including the app's own messages: `channel.message.created`, `channel.message.updated` and `channel.message.deleted`. Acknowledge with a 2xx `status` within 10 seconds.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "<event id>",
|
||||
"type": "channel.message.created",
|
||||
"channel": { "id": "<channel id>" },
|
||||
"message": {
|
||||
"id": "<id>", "channel_id": "<id>", "author": { "type": "user", "id": "<id>" },
|
||||
"content": "...", "parent_id": null, "reply_to_id": null, "files": [], "actions": [], "embeds": [],
|
||||
"created_at": "<ns>", "updated_at": "<ns>", "etag": "<ns>"
|
||||
},
|
||||
"event_token": "<token>",
|
||||
"app_id": "<app id>",
|
||||
"installation_id": "<installation id>"
|
||||
}
|
||||
```
|
||||
|
||||
`author.type` is `user`, `model`, `webhook` or `app`. `model` and `webhook` follow the label stored on the message, which any poster can set, so treat them as presentation. For a message with a model label, `channel.message.updated` is sent only once the message is marked done, which for a model's reply means once the reply is complete. Deleted-message events carry only `id`, `channel_id`, `parent_id`, `author` and `updated_at`. Message events are best effort: each is sent once, and events that fail or occur while the app is disconnected are dropped.
|
||||
|
||||
A mention reaches an app with `channels:messages:read` twice: as `channel.mention` and as `channel.message.created`. Answer it in one of the two handlers.
|
||||
|
||||
`execution_token` and `event_token` are signed by Open WebUI. The app checks them by sending them back with the `authorize` or `verify_event` operation below. An `execution_token` is valid for 24 hours and an `event_token` for 5 minutes. Both stop working when the app's API key or public key changes.
|
||||
|
||||
## Replying to interactions
|
||||
|
||||
Acknowledge an interaction with `{"status": 200, "data": <reply>}`, at most 256 KiB:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 200,
|
||||
"data": {
|
||||
"type": "message",
|
||||
"visibility": "channel",
|
||||
"content": "Ready to publish?",
|
||||
"actions": [{ "action_id": "publish", "label": "Publish", "context": { "draft": 42 } }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `type` | `message` (default), `update`, `ack`, `download` or `error`. |
|
||||
| `visibility` | `private` (default, only the user who mentioned or clicked) or `channel`. |
|
||||
| `content` | Markdown, up to 32,000 characters. |
|
||||
| `title` | Optional heading for private replies, up to 200 characters. |
|
||||
| `actions` | Up to 30 buttons. Each has a `label` (up to 200 characters) and either an `action_id` (up to 100 characters) with an optional `context` object that comes back in `channel.action`, or a `url`: an `http(s)` link of up to 2,048 characters, without credentials, that opens in a new tab. Only allowed on `message` or `update` with `visibility` `channel`. |
|
||||
| `embeds` | Up to 10 cards, on `message` or `update`. Each takes `title` (up to 256 characters), `description` (Markdown, up to 4,096), `url` (links the title), `color` (an integer from `0` to `0xFFFFFF` for the accent bar), `author` (`{"name": ...}`, up to 256), `footer` (`{"text": ...}`, up to 2,048), `thumbnail` (`{"url": ...}`) and up to 25 `fields` (`name` up to 256, `value` in Markdown up to 1,024, optional `inline`). All cards in one message hold at most 6,000 characters of text. URLs are `http(s)` without credentials, up to 2,048 characters. Users' browsers load thumbnails directly. |
|
||||
| `parent_id`, `reply_to_id` | Post a channel reply into a thread or as a reply to a message. A channel reply without `parent_id` lands at the top of the channel. |
|
||||
| `filename` | For `download`: letters, digits, `_`, `.` and `-`, ending in `.md`. Default `report.md`. |
|
||||
|
||||
- `message` with `visibility` `channel` posts a channel message and needs `channels:messages:write`.
|
||||
- `update` replaces the content, buttons and cards of the message whose button was clicked. It only works for `channel.action` and needs `channels:messages:write`. Set `visibility` to `channel`: with the default `private`, buttons are refused and the user also gets a private copy of the content.
|
||||
- `download` gives the user the `content` as a Markdown file.
|
||||
- `error` shows the `content` with a failure notice.
|
||||
- Send `download` and `error` with `visibility` `private`, the default. With `channel`, a `download` is shown nowhere and an `error` is shown only after a button click.
|
||||
- `ack` shows nothing. Send it as `{"type": "ack"}`.
|
||||
|
||||
Unknown fields make the reply invalid. A non-200 `status` is shown to the user as an error.
|
||||
|
||||
After a mention, a failed reply shows only a generic error in the channel. A button click shows the specific reason under the message, which helps while testing a reply. [Troubleshooting](/features/channels/apps#troubleshooting) lists the messages admins and users see.
|
||||
|
||||
## Calling Open WebUI
|
||||
|
||||
The app emits `app:request` on the same connection with `{"operation": "<name>", "arguments": {...}}`. The acknowledgement is `{"status": 200, "data": ...}` or `{"status": <code>, "error": "<message>"}`. Each request must finish within 10 seconds. Requests act only in channels the app is a member of.
|
||||
|
||||
| Operation | Arguments | Scope | Returns |
|
||||
|-----------|-----------|-------|---------|
|
||||
| `ping` | `{}` | none | `{"received": true}` |
|
||||
| `get_me` | none | none | The app's record |
|
||||
| `list_channels` | none | `channels:read` | Joined channels: `id`, `name`, `type`, `is_private` |
|
||||
| `list_messages` | `channel_id`, optional `parent_id`, `before`, `limit` (1 to 100, default 50) | `channels:messages:read` | `messages` newest first and `next_cursor` |
|
||||
| `create_message` | `channel_id`, `content`, optional `actions`, `embeds`, `parent_id`, `reply_to_id` | `channels:messages:write` | The new message, including its `etag` |
|
||||
| `update_message` | `message_id`, `etag`, `content`, optional `actions`, `embeds` | `channels:messages:write` | The updated message |
|
||||
| `authorize` | `token`: an `execution_token` | the interaction's scope | `authorized`, `operation_id`, `actor`, `channel_id`, `type`, `data` |
|
||||
| `verify_event` | `token`: an `event_token` | `channels:messages:read` | The verified event |
|
||||
|
||||
`update_message` edits only the app's own messages. `content`, `actions` and `embeds` replace the old values, so omitted `actions` or `embeds` are removed. If the message changed since the `etag` was read, the request fails with status 412 (`Message changed`).
|
||||
|
||||
## Long-running work
|
||||
|
||||
An interaction must be answered within 3 seconds. For anything slower:
|
||||
|
||||
1. Reply `{"type": "ack"}` right away. The user sees nothing.
|
||||
2. Do the work.
|
||||
3. Optionally call `authorize` with the event's `execution_token` (valid for 24 hours) to confirm the user can still use the app and read the channel.
|
||||
4. Post the result with the `create_message` operation, using `data.parent_id` from the event to stay in the thread. This needs `channels:messages:write`, and the result is a channel message that everyone in the channel sees. A private reply is only possible as the direct answer to an interaction.
|
||||
|
||||
## Limits
|
||||
|
||||
| Limit | Value |
|
||||
|-------|-------|
|
||||
| Socket.IO packet | 16 MiB, JSON text only. Binary packets are rejected. |
|
||||
| Event sent to the app | 1 MiB |
|
||||
| Reply to an event | 256 KiB |
|
||||
| Manifest response | 256 KiB, within 10 seconds |
|
||||
| Message content | 32,000 characters, up to 30 buttons and 10 cards with 6,000 characters of card text |
|
||||
| Connection and handshake | 20 seconds in total, 10 seconds per acknowledgement |
|
||||
| Interaction answer time | 3 seconds |
|
||||
| Message event answer time | 10 seconds |
|
||||
| `app:request` processing | 10 seconds |
|
||||
| `execution_token` lifetime | 24 hours |
|
||||
| `event_token` lifetime | 5 minutes |
|
||||
@@ -56,6 +56,7 @@ Instant updates, emoji reactions, threaded replies, pinned messages, and unread
|
||||
| 📝 **Message queue** | Keep sending while a message or file upload is still in progress: new messages queue above the input, can be edited or removed and go out in order |
|
||||
| 🔒 **Access control** | Public, private, group-based, and direct message channels |
|
||||
| 🧠 **AI channel awareness** | Models can search and synthesize across channels autonomously |
|
||||
| 🧩 **[Apps](/features/channels/apps)** | External services join channels, answer `@mentions` and post messages with buttons |
|
||||
|
||||
---
|
||||
|
||||
@@ -119,12 +120,16 @@ Hover any message to react with emoji, pin it for reference, reply inline, or st
|
||||
|
||||
### Bots posting as a model
|
||||
|
||||
Any account can post into a channel over the API, which is how bot integrations work: the bot runs under its own account and posts like any other member. [open-webui/bot](https://github.com/open-webui/bot) is an experimental starting point for building one.
|
||||
Any account can post into a channel over the API: a script or bot runs under its own account and posts like any other member. A bot that answers `@mentions` under its own name is an [App](#apps).
|
||||
|
||||
A posted message can carry an optional model identity, and the interface then renders that message under the model's name and avatar in place of the posting account's. A bot that answers on behalf of a model sets this so its replies are presented as that model's replies.
|
||||
|
||||
That identity is a label chosen by whoever posts the message, and it is not evidence that the server ran the model. The account that posted stays the recorded author and is what the API returns for the message, so the real sender can always be recovered. Anyone who can post in a channel can set the label, so treat the model name next to a message as presentation only.
|
||||
|
||||
### Apps
|
||||
|
||||
External services can also join a channel as [Apps](/features/channels/apps). An admin installs them in **Settings > Admin > Apps**; the channel's creator or an admin then adds them to the channel. They show up in the member list with an **App** badge, answer when you `@mention` them and can post messages with buttons.
|
||||
|
||||
### Direct messages
|
||||
|
||||
A direct message is a conversation between you and the people you pick, and there is one of them per set of participants. Start one from the **Message** button on someone's profile card in a channel, or from **(+)** in the sidebar's **Channels** section with **Channel Type** set to **Direct Message**. Messaging someone you have messaged before reopens the conversation you already have with them, with its history, instead of adding a second one beside it. A name is optional on a direct message; without one it is listed under the names of the other participants.
|
||||
|
||||
@@ -126,6 +126,7 @@ Persistent, shared spaces where humans and AI models participate in the same con
|
||||
| 🧵 **Threads & reactions** | Replies, pins, and emoji reactions |
|
||||
| 🔒 **Access control** | Public, private, group-based, and direct message channels |
|
||||
| 🧠 **AI channel awareness** | Models search and synthesize across channels autonomously |
|
||||
| 🧩 **[Apps](/features/channels/apps)** | External services join channels, answer `@mentions` and post messages with buttons |
|
||||
|
||||
[**Learn about Channels →**](/features/channels)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ The `WEBUI_SECRET_KEY` is used to sign JWTs (login tokens) and derive encryption
|
||||
|
||||
If you run multiple Open WebUI instances behind a load balancer, every instance must share the same key. Otherwise, a token signed by one instance will be rejected by another, causing login failures. Generate a key with `openssl rand -base64 32` and pass it as an environment variable to all replicas.
|
||||
|
||||
**Rotation:** Changing the key invalidates all existing sessions. Users will need to sign in again. If [two-factor sign-in](#two-factor-sign-in) is on, users' authenticator secrets are encrypted with a key derived from `WEBUI_SECRET_KEY`, and once users have enrolled, Open WebUI refuses to start after a rotation. Set [`MFA_ENCRYPTION_KEY`](/reference/env-configuration#mfa_encryption_key) before users enroll to rotate `WEBUI_SECRET_KEY` freely.
|
||||
**Rotation:** Changing the key invalidates all existing sessions. Users will need to sign in again. If [two-factor sign-in](#two-factor-sign-in) is on, users' authenticator secrets are encrypted with a key derived from `WEBUI_SECRET_KEY`, and once users have enrolled, Open WebUI refuses to start after a rotation. Set [`MFA_ENCRYPTION_KEY`](/reference/env-configuration#mfa_encryption_key) before users enroll to rotate `WEBUI_SECRET_KEY` freely. Installed [apps](/features/channels/apps) stop connecting after a rotation; uninstall each app, connect it again with its API key and add it back to its channels (see [Apps stop connecting after WEBUI_SECRET_KEY changed](/features/channels/apps#apps-stop-connecting-after-webui_secret_key-changed)).
|
||||
|
||||
---
|
||||
|
||||
@@ -586,6 +586,8 @@ AIOHTTP_CLIENT_ALLOW_REDIRECTS=false
|
||||
Earlier versions applied URL validation and the redirect gate only to the default web loader; the Playwright-based loader (`WEB_LOADER_ENGINE=playwright` / the `playwright` Docker variant) could navigate and follow redirects to internal or blocklisted URLs unchecked. As of v0.9.6 the Playwright path enforces the same `validate_url()` and redirect rules as the default loader, so the SSRF controls above apply regardless of which web loader engine you run. If you use Playwright, ensure you are on v0.9.6 or later.
|
||||
:::
|
||||
|
||||
[Apps](/features/channels/apps) have their own gate: an app URL must use HTTPS and resolve to public addresses, rejects redirects and always verifies TLS. [`APP_ALLOWED_PRIVATE_ORIGINS`](/reference/env-configuration#app_allowed_private_origins) lists the app origins allowed on private addresses or plain HTTP.
|
||||
|
||||
### Profile image URL forwarding
|
||||
|
||||
The user and model profile-image endpoints can issue a `302 Found` redirect to whatever origin is stored in `profile_image_url` so that externally-hosted avatars (e.g. Gravatar via an upstream identity provider) display in the UI. That redirect causes the user's browser to make a request directly to the external origin, leaking client IP, User-Agent, and Referer headers. An account whose `profile_image_url` was set to an attacker-controlled host can use that to deanonymize anyone who renders their avatar.
|
||||
|
||||
@@ -100,6 +100,7 @@ Here is a complete list of tables in Open-WebUI's SQLite database. The tables ar
|
||||
| 44 | tool_history | Saved versions of tool source code and metadata |
|
||||
| 45 | function_history | Saved versions of function source code and metadata |
|
||||
| 46 | skill_history | Saved versions of skills, including their supporting files |
|
||||
| 47 | app | Installed [Apps](/features/channels/apps) and their encrypted credentials |
|
||||
|
||||
Note: there are two additional tables in Open-WebUI's SQLite database that are not related to Open-WebUI's core functionality, that have been excluded:
|
||||
|
||||
@@ -154,6 +155,29 @@ Things to know about the api_key table:
|
||||
- Key generation, retrieval and deletion all go through `/api/v1/auths/api_key`. All three are refused unless the `auth.enable_api_keys` config key is on, and non-admins additionally need the `features.api_keys` permission.
|
||||
- `data`, `expires_at` and `last_used_at` are present in the schema and no code path writes them. Key lookup joins on `key` alone and does not consult `expires_at`.
|
||||
|
||||
## App Table
|
||||
|
||||
Added in v0.12.1 (migration `c92da83b017f`) for [Apps](/features/channels/apps).
|
||||
|
||||
| **Column Name** | **Data Type** | **Constraints** | **Description** |
|
||||
| --------------- | ------------- | ---------------------------- | -------------------------------------------------- |
|
||||
| id | Text | PRIMARY KEY | Unique identifier (UUID), the `app_id` sent to the app |
|
||||
| slug | Text | UNIQUE, NOT NULL | The app handle chosen at install |
|
||||
| name | Text | NOT NULL | Display name |
|
||||
| user_id | Text | nullable | Admin who installed the app |
|
||||
| is_active | Boolean | NOT NULL, server_default=false | Whether the app is enabled |
|
||||
| data | JSON | NOT NULL | App URL, manifest, granted scopes, settings, installation ID and the encrypted connection credential |
|
||||
| api_key | Text | nullable | The app's API key, encrypted |
|
||||
| created_at | BigInteger | NOT NULL | Creation timestamp (nanoseconds) |
|
||||
| updated_at | BigInteger | NOT NULL | Last update timestamp (nanoseconds) |
|
||||
|
||||
Things to know about the app table:
|
||||
|
||||
- `api_key` and the connection credential in `data` are encrypted with a key derived from `WEBUI_SECRET_KEY`.
|
||||
- Uninstalling an app keeps the row and its `slug`: it clears `api_key`, the connection credential and the installation ID, sets `is_active` to false and deactivates the app's channel memberships.
|
||||
- Who can use an app is stored in the `access_grant` table with `resource_type = 'app'`.
|
||||
- Downgrading past `c92da83b017f` is refused while any row in `message` has an `app_id`.
|
||||
|
||||
## Auth Table
|
||||
|
||||
| **Column Name** | **Data Type** | **Constraints** | **Description** |
|
||||
@@ -206,7 +230,8 @@ Things to know about the channel table:
|
||||
| ----------------- | ------------- | ------------------------------- | -------------------------------------------- |
|
||||
| id | Text | PRIMARY KEY, UNIQUE | Unique identifier for the channel membership |
|
||||
| channel_id | Text | NOT NULL | Reference to the channel |
|
||||
| user_id | Text | NOT NULL | Reference to the user |
|
||||
| user_id | Text | nullable | Reference to the user |
|
||||
| app_id | Text | nullable | Reference to the app, for app memberships |
|
||||
| role | Text | nullable | Member's role within the channel |
|
||||
| status | Text | nullable | Membership status: `joined` or `left` |
|
||||
| is_active | Boolean | NOT NULL, server_default=true | Whether the membership is live |
|
||||
@@ -228,9 +253,11 @@ Things to know about the channel_member table:
|
||||
- `status` is written as `joined` when the membership is created. Leaving a channel writes `left`, sets `is_active` to false and stamps `left_at`. The row survives.
|
||||
- `is_active` is the flag that channel listings and member listings filter on, so a membership someone has left drops out of those results while remaining in the table. It is also written by `POST /api/v1/channels/{id}/members/active`.
|
||||
- `last_read_at` is stamped when the membership is created and updated over the socket connection when the user reads the channel.
|
||||
- `role` is read in one place, to test whether a member is a `manager`. No code path writes it, so it is null on every row and that test never matches. Manager-only actions such as viewing and creating channel webhooks fall through to the other two branches of the check: the channel's own `user_id`, or an instance admin.
|
||||
- `role` is read in one place, to test whether a member is a `manager`. Nothing writes `manager`: user memberships leave `role` null and app memberships are written as `member`, so that test never matches. Manager-only actions such as viewing and creating channel webhooks fall through to the other two branches of the check: the channel's own `user_id`, or an instance admin.
|
||||
- `is_channel_muted` has no write path and stays false. `is_channel_pinned` has a setter on the model, `Channels.pin_channel`, that nothing calls.
|
||||
- `joined_at` is NOT NULL in the database (migration `2f1211949ecc`) while the SQLAlchemy model omits the flag. The database constraint is the one that applies.
|
||||
- Removing an app from a channel, or uninstalling the app, sets `is_active` to false and stamps `left_at`; `status` stays `joined`. Adding the app again reactivates the same row.
|
||||
- Migration `c92da83b017f` made `user_id` nullable and added `app_id`. A check constraint requires exactly one of the two on every row, and (`channel_id`, `app_id`) is unique.
|
||||
- Deleting a user account leaves their `channel_member` rows in place. Member listings, the member count on a channel and the lookup that finds the existing direct message for a set of people all skip rows whose `user_id` no longer matches an account, so a leftover row is not counted as a member and does not push a direct message into a second conversation.
|
||||
|
||||
## Channel Webhook Table
|
||||
@@ -737,6 +764,7 @@ Things to know about the memory table:
|
||||
| --------------- | ------------- | --------------- | ----------------------------------- |
|
||||
| id | Text | PRIMARY KEY | Unique identifier (UUID) |
|
||||
| user_id | Text | - | Message author |
|
||||
| app_id | Text | nullable | App that posted the message |
|
||||
| channel_id | Text | nullable | Associated channel |
|
||||
| reply_to_id | Text | nullable | Message this one quotes in its reply |
|
||||
| parent_id | Text | nullable | Thread root for threaded replies |
|
||||
@@ -754,6 +782,7 @@ Things to know about the message table:
|
||||
- `parent_id` was added in migration `3781e22d8b01` and `reply_to_id` in migration `a5c220713937`. `is_pinned`, `pinned_at` and `pinned_by` were added in migration `2f1211949ecc`.
|
||||
- `parent_id` and `reply_to_id` do different jobs. `parent_id` is the thread root: a channel's message list selects rows with a null `parent_id`, and a thread is read by selecting rows whose `parent_id` is the root message. `reply_to_id` points at a single message that the reply quotes, and the quoted message is fetched and attached when a message is served. It has no effect on which list a message appears in.
|
||||
- `is_pinned`, `pinned_at` and `pinned_by` are written together by `POST /api/v1/channels/{id}/messages/{message_id}/pin`. Unpinning clears `pinned_at` and `pinned_by` back to null. `GET /api/v1/channels/{id}/messages/pinned` reads them.
|
||||
- `app_id` was added in migration `c92da83b017f`. Messages posted by an app carry its `app_id` and a null `user_id`.
|
||||
- `meta` carries the webhook identity for messages posted through a [channel webhook](#channel-webhook-table), under a `webhook` key, which is what lets those messages render with the webhook's name rather than a user account.
|
||||
|
||||
## Message Reaction Table
|
||||
|
||||
@@ -333,6 +333,20 @@ is also being used and set to `True`. **Never disable this if OAUTH/SSO is not b
|
||||
- Description: Enables or disables channel support.
|
||||
- Persistence: This environment variable is a `ConfigVar` variable.
|
||||
|
||||
#### `ENABLE_APPS`
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `False`
|
||||
- Description: Enables [Apps](/features/channels/apps): external services that join channels as members, answer mentions and post messages with buttons. Adds the **Apps** tab to the admin settings, where admins install and connect apps. Apps act only in channels, so [`ENABLE_CHANNELS`](#enable_channels) must be on too. Turning it off closes every app connection. Can also be set in **Settings > Admin > General** (**Apps**).
|
||||
- Persistence: This environment variable is a `ConfigVar` variable.
|
||||
|
||||
#### `APP_ALLOWED_PRIVATE_ORIGINS`
|
||||
|
||||
- Type: `str` (comma-separated list of origins)
|
||||
- Default: Empty string (' ')
|
||||
- Example: `http://127.0.0.1:8000,https://apps.internal.example`
|
||||
- Description: App origins (scheme, host and port) that [Apps](/features/channels/apps#private-and-plain-http-app-servers) may reach on private, loopback or reserved addresses. A listed `http://` origin may also use plain HTTP. Every other app URL must use HTTPS and resolve only to public addresses. The match is exact on scheme, host and port; without a port, `https` means 443 and `http` means 80. Write every entry with its scheme, for example `http://host.docker.internal:8000`. An entry without a scheme, or with credentials or a fragment, breaks every app, public ones included: loading app details fails with `Invalid app URL` and installed apps stay on **Connecting**.
|
||||
|
||||
#### `CHANNEL_MODEL_RESPONSE_MODE`
|
||||
|
||||
- Type: `str`
|
||||
@@ -2528,7 +2542,7 @@ When setting a custom `PASSWORD_VALIDATION_REGEX_PATTERN`, always set `PASSWORD_
|
||||
|
||||
- Type: `str`
|
||||
- Default: Automatically generated (see below)
|
||||
- Description: The secret key used for signing JSON Web Tokens (JWTs) and **encrypting sensitive data** at rest (including OAuth tokens for MCP). Can be set via the `WEBUI_SECRET_KEY` environment variable or the legacy `WEBUI_JWT_SECRET_KEY` variable (deprecated).
|
||||
- Description: The secret key used for signing JSON Web Tokens (JWTs) and **encrypting sensitive data** at rest (including OAuth tokens for MCP and app API keys). Can be set via the `WEBUI_SECRET_KEY` environment variable or the legacy `WEBUI_JWT_SECRET_KEY` variable (deprecated).
|
||||
|
||||
:::info How the secret key is generated
|
||||
|
||||
@@ -2555,6 +2569,7 @@ While the auto-generated key is secure, it is tied to a file inside the containe
|
||||
2. **All OAuth sessions become invalid.**
|
||||
3. **MCP Tools break** (Error: `Error decrypting tokens`) because tokens encrypted with the previous key cannot be decrypted.
|
||||
4. **Open WebUI refuses to start while [MFA](/features/authentication-access/mfa) is on**, because users' authenticator secrets were encrypted with the previous key, unless [`MFA_ENCRYPTION_KEY`](#mfa_encryption_key) is set.
|
||||
5. **Installed [apps](/features/channels/apps) stop connecting**, because their API keys and connection credentials were encrypted with the previous key. See [Apps stop connecting after WEBUI_SECRET_KEY changed](/features/channels/apps#apps-stop-connecting-after-webui_secret_key-changed).
|
||||
|
||||
To avoid this, explicitly set `WEBUI_SECRET_KEY` to a secure, persistent value that survives container recreates:
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ If you believe you have found a security issue that
|
||||
|
||||
8. **CVSS Scoring Accuracy:** You do not have to include a CVSS score in your report. If you leave the CVSS section empty, we will fill it out for you prior to publishing. If you include a CVSS score with your report, it must accurately reflect the vulnerability according to CVSS methodology. In case of inaccurate CVSS, we will adjust the CVSS score of your report. If you cite other CVEs to support your report, ensure they are **genuinely comparable** in vulnerability type, threat model, and attack vector.
|
||||
|
||||
9. **Admin Actions Are Out of Scope:** Vulnerabilities that require an administrator to actively perform unsafe actions are **not considered valid vulnerabilities**. **Admins have full system control and are expected to understand the security implications of their actions and configurations**. This includes but is not limited to: adding malicious external servers (models, tools, webhooks, functions), pasting untrusted code into Functions/Tools, or intentionally weakening security settings. **Reports requiring admin negligence or social engineering of admins may be rejected.**
|
||||
9. **Admin Actions Are Out of Scope:** Vulnerabilities that require an administrator to actively perform unsafe actions are **not considered valid vulnerabilities**. **Admins have full system control and are expected to understand the security implications of their actions and configurations**. This includes but is not limited to: adding malicious external servers (models, tools, webhooks, apps, functions), pasting untrusted code into Functions/Tools, or intentionally weakening security settings. **Reports requiring admin negligence or social engineering of admins may be rejected.**
|
||||
|
||||
:::note
|
||||
|
||||
|
||||
@@ -340,6 +340,8 @@ alembic upgrade <revision>
|
||||
Downgrading can cause **permanent data loss** if the migration removed columns or tables. Only downgrade if you understand the consequences.
|
||||
:::
|
||||
|
||||
Downgrading past the [Apps](/features/channels/apps) migration (`c92da83b017f`) stops with `Export/remove app messages before downgrading apps` while any message posted by an app exists. Once those messages are gone it removes the app table and every app channel membership.
|
||||
|
||||
```bash title="Terminal - Downgrade Migrations"
|
||||
# Downgrade one version
|
||||
alembic downgrade -1
|
||||
|
||||
Reference in new issue
Block a user