Files
openclaw/docs/gateway/configuration-examples.md

621 lines
14 KiB
Markdown
Raw Normal View History

---
2026-01-30 03:15:10 +01:00
summary: "Schema-accurate configuration examples for common OpenClaw setups"
read_when:
2026-01-30 03:15:10 +01:00
- Learning how to configure OpenClaw
- Looking for configuration examples
2026-01-30 03:15:10 +01:00
- Setting up OpenClaw for the first time
title: "Configuration Examples"
---
2026-01-31 21:13:13 +09:00
# Configuration Examples
Examples below are aligned with the current config schema. For the exhaustive reference and per-field notes, see [Configuration](/gateway/configuration).
## Quick start
### Absolute minimum
2026-01-31 21:13:13 +09:00
```json5
{
2026-01-30 03:15:10 +01:00
agent: { workspace: "~/.openclaw/workspace" },
2026-01-31 21:13:13 +09:00
channels: { whatsapp: { allowFrom: ["+15555550123"] } },
}
```
2026-01-30 03:15:10 +01:00
Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
### Recommended starter
2026-01-31 21:13:13 +09:00
```json5
{
identity: {
name: "Clawd",
theme: "helpful assistant",
2026-01-31 21:13:13 +09:00
emoji: "🦞",
},
agent: {
2026-01-30 03:15:10 +01:00
workspace: "~/.openclaw/workspace",
2026-01-31 21:13:13 +09:00
model: { primary: "anthropic/claude-sonnet-4-5" },
},
channels: {
whatsapp: {
allowFrom: ["+15555550123"],
2026-01-31 21:13:13 +09:00
groups: { "*": { requireMention: true } },
},
},
}
```
## Expanded example (major options)
> JSON5 lets you use comments and trailing commas. Regular JSON works too.
```json5
{
// Environment + shell
env: {
2026-01-08 22:37:06 +01:00
OPENROUTER_API_KEY: "sk-or-...",
vars: {
2026-01-31 21:13:13 +09:00
GROQ_API_KEY: "gsk-...",
2026-01-08 22:37:06 +01:00
},
shellEnv: {
enabled: true,
2026-01-31 21:13:13 +09:00
timeoutMs: 15000,
},
},
// Auth profile metadata (secrets live in auth-profiles.json)
auth: {
profiles: {
"anthropic:me@example.com": {
provider: "anthropic",
mode: "oauth",
email: "me@example.com",
},
"anthropic:work": { provider: "anthropic", mode: "api_key" },
"openai:default": { provider: "openai", mode: "api_key" },
2026-01-31 21:13:13 +09:00
"openai-codex:default": { provider: "openai-codex", mode: "oauth" },
},
order: {
anthropic: ["anthropic:me@example.com", "anthropic:work"],
openai: ["openai:default"],
2026-01-31 21:13:13 +09:00
"openai-codex": ["openai-codex:default"],
},
},
// Identity
identity: {
name: "Samantha",
theme: "helpful sloth",
2026-01-31 21:13:13 +09:00
emoji: "🦥",
},
// Logging
logging: {
level: "info",
2026-01-30 03:15:10 +01:00
file: "/tmp/openclaw/openclaw.log",
consoleLevel: "info",
consoleStyle: "pretty",
2026-01-31 21:13:13 +09:00
redactSensitive: "tools",
},
// Message formatting
messages: {
2026-01-30 03:15:10 +01:00
messagePrefix: "[openclaw]",
responsePrefix: ">",
ackReaction: "👀",
2026-01-31 21:13:13 +09:00
ackReactionScope: "group-mentions",
},
// Routing + queue
routing: {
groupChat: {
2026-01-30 03:15:10 +01:00
mentionPatterns: ["@openclaw", "openclaw"],
2026-01-31 21:13:13 +09:00
historyLimit: 50,
},
queue: {
mode: "collect",
debounceMs: 1000,
cap: 20,
drop: "summarize",
2026-01-13 07:15:57 +00:00
byChannel: {
whatsapp: "collect",
telegram: "collect",
discord: "collect",
slack: "collect",
signal: "collect",
imessage: "collect",
2026-01-31 21:13:13 +09:00
webchat: "collect",
},
},
},
// Tooling
tools: {
media: {
audio: {
enabled: true,
maxBytes: 20971520,
models: [
2026-01-23 05:47:16 +00:00
{ provider: "openai", model: "gpt-4o-mini-transcribe" },
// Optional CLI fallback (Whisper binary):
// { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }
],
2026-01-31 21:13:13 +09:00
timeoutSeconds: 120,
},
video: {
enabled: true,
maxBytes: 52428800,
2026-01-31 21:13:13 +09:00
models: [{ provider: "google", model: "gemini-3-flash-preview" }],
},
},
},
// Session behavior
session: {
scope: "per-sender",
reset: {
mode: "daily",
atHour: 4,
2026-01-31 21:13:13 +09:00
idleMinutes: 60,
},
resetByChannel: {
2026-01-31 21:13:13 +09:00
discord: { mode: "idle", idleMinutes: 10080 },
},
resetTriggers: ["/new", "/reset"],
2026-01-30 03:15:10 +01:00
store: "~/.openclaw/agents/default/sessions/sessions.json",
fix: unify session maintenance and cron run pruning (#13083) * fix: prune stale session entries, cap entry count, and rotate sessions.json The sessions.json file grows unbounded over time. Every heartbeat tick (default: 30m) triggers multiple full rewrites, and session keys from groups, threads, and DMs accumulate indefinitely with large embedded objects (skillsSnapshot, systemPromptReport). At >50MB the synchronous JSON parse blocks the event loop, causing Telegram webhook timeouts and effectively taking the bot down. Three mitigations, all running inside saveSessionStoreUnlocked() on every write: 1. Prune stale entries: remove entries with updatedAt older than 30 days (configurable via session.maintenance.pruneDays in openclaw.json) 2. Cap entry count: keep only the 500 most recently updated entries (configurable via session.maintenance.maxEntries). Entries without updatedAt are evicted first. 3. File rotation: if the existing sessions.json exceeds 10MB before a write, rename it to sessions.json.bak.{timestamp} and keep only the 3 most recent backups (configurable via session.maintenance.rotateBytes). All three thresholds are configurable under session.maintenance in openclaw.json with Zod validation. No env vars. Existing tests updated to use Date.now() instead of epoch-relative timestamps (1, 2, 3) that would be incorrectly pruned as stale. 27 new tests covering pruning, capping, rotation, and integration scenarios. * feat: auto-prune expired cron run sessions (#12289) Add TTL-based reaper for isolated cron run sessions that accumulate indefinitely in sessions.json. New config option: cron.sessionRetention: string | false (default: '24h') The reaper runs piggy-backed on the cron timer tick, self-throttled to sweep at most every 5 minutes. It removes session entries matching the pattern cron:<jobId>:run:<uuid> whose updatedAt + retention < now. Design follows the Kubernetes ttlSecondsAfterFinished pattern: - Sessions are persisted normally (observability/debugging) - A periodic reaper prunes expired entries - Configurable retention with sensible default - Set to false to disable pruning entirely Files changed: - src/config/types.cron.ts: Add sessionRetention to CronConfig - src/config/zod-schema.ts: Add Zod validation for sessionRetention - src/cron/session-reaper.ts: New reaper module (sweepCronRunSessions) - src/cron/session-reaper.test.ts: 12 tests covering all paths - src/cron/service/state.ts: Add cronConfig/sessionStorePath to deps - src/cron/service/timer.ts: Wire reaper into onTimer tick - src/gateway/server-cron.ts: Pass config and session store path to deps Closes #12289 * fix: sweep cron session stores per agent * docs: add changelog for session maintenance (#13083) (thanks @skyfallsin, @Glucksberg) * fix: add warn-only session maintenance mode * fix: warn-only maintenance defaults to active session * fix: deliver maintenance warnings to active session * docs: add session maintenance examples * fix: accept duration and size maintenance thresholds * refactor: share cron run session key check * fix: format issues and replace defaultRuntime.warn with console.warn --------- Co-authored-by: Pradeep Elankumaran <pradeepe@gmail.com> Co-authored-by: Glucksberg <markuscontasul@gmail.com> Co-authored-by: max <40643627+quotentiroler@users.noreply.github.com> Co-authored-by: quotentiroler <max.nussbaumer@maxhealth.tech>
2026-02-09 23:42:35 -05:00
maintenance: {
mode: "warn",
pruneAfter: "30d",
maxEntries: 500,
rotateBytes: "10mb",
},
typingIntervalSeconds: 5,
sendPolicy: {
default: "allow",
2026-01-31 21:13:13 +09:00
rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }],
},
},
2026-01-13 07:15:57 +00:00
// Channels
channels: {
whatsapp: {
dmPolicy: "pairing",
allowFrom: ["+15555550123"],
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
2026-01-31 21:13:13 +09:00
groups: { "*": { requireMention: true } },
},
telegram: {
enabled: true,
botToken: "YOUR_TELEGRAM_BOT_TOKEN",
allowFrom: ["123456789"],
groupPolicy: "allowlist",
groupAllowFrom: ["123456789"],
2026-01-31 21:13:13 +09:00
groups: { "*": { requireMention: true } },
},
discord: {
enabled: true,
token: "YOUR_DISCORD_BOT_TOKEN",
dm: { enabled: true, allowFrom: ["steipete"] },
guilds: {
"123456789012345678": {
2026-01-30 03:15:10 +01:00
slug: "friends-of-openclaw",
requireMention: false,
channels: {
general: { allow: true },
2026-01-31 21:13:13 +09:00
help: { allow: true, requireMention: true },
},
},
},
},
slack: {
enabled: true,
botToken: "xoxb-REPLACE_ME",
appToken: "xapp-REPLACE_ME",
channels: {
2026-01-31 21:13:13 +09:00
"#general": { allow: true, requireMention: true },
},
dm: { enabled: true, allowFrom: ["U123"] },
slashCommand: {
enabled: true,
2026-01-30 03:15:10 +01:00
name: "openclaw",
sessionPrefix: "slack:slash",
2026-01-31 21:13:13 +09:00
ephemeral: true,
},
},
},
// Agent runtime
agents: {
defaults: {
2026-01-30 03:15:10 +01:00
workspace: "~/.openclaw/workspace",
userTimezone: "America/Chicago",
model: {
primary: "anthropic/claude-sonnet-4-5",
fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
},
imageModel: {
2026-01-31 21:13:13 +09:00
primary: "openrouter/anthropic/claude-sonnet-4-5",
},
models: {
"anthropic/claude-opus-4-6": { alias: "opus" },
"anthropic/claude-sonnet-4-5": { alias: "sonnet" },
2026-01-31 21:13:13 +09:00
"openai/gpt-5.2": { alias: "gpt" },
},
thinkingDefault: "low",
verboseDefault: "off",
elevatedDefault: "on",
blockStreamingDefault: "off",
blockStreamingBreak: "text_end",
blockStreamingChunk: {
minChars: 800,
maxChars: 1200,
2026-01-31 21:13:13 +09:00
breakPreference: "paragraph",
},
blockStreamingCoalesce: {
2026-01-31 21:13:13 +09:00
idleMs: 1000,
},
humanDelay: {
2026-01-31 21:13:13 +09:00
mode: "natural",
},
timeoutSeconds: 600,
mediaMaxMb: 5,
typingIntervalSeconds: 5,
maxConcurrent: 3,
heartbeat: {
every: "30m",
model: "anthropic/claude-sonnet-4-5",
target: "last",
to: "+15555550123",
prompt: "HEARTBEAT",
2026-01-31 21:13:13 +09:00
ackMaxChars: 300,
},
memorySearch: {
2026-01-18 09:09:13 +00:00
provider: "gemini",
model: "gemini-embedding-001",
remote: {
2026-01-31 21:13:13 +09:00
apiKey: "${GEMINI_API_KEY}",
},
2026-01-31 21:13:13 +09:00
extraPaths: ["../team-docs", "/srv/shared-notes"],
},
sandbox: {
mode: "non-main",
perSession: true,
2026-01-30 03:15:10 +01:00
workspaceRoot: "~/.openclaw/sandboxes",
docker: {
2026-01-30 03:15:10 +01:00
image: "openclaw-sandbox:bookworm-slim",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
2026-01-31 21:13:13 +09:00
user: "1000:1000",
},
browser: {
2026-01-31 21:13:13 +09:00
enabled: false,
},
},
},
},
tools: {
allow: ["exec", "process", "read", "write", "edit", "apply_patch"],
deny: ["browser", "canvas"],
exec: {
backgroundMs: 10000,
timeoutSec: 1800,
2026-01-31 21:13:13 +09:00
cleanupMs: 1800000,
},
elevated: {
enabled: true,
allowFrom: {
whatsapp: ["+15555550123"],
telegram: ["123456789"],
discord: ["steipete"],
slack: ["U123"],
signal: ["+15555550123"],
imessage: ["user@example.com"],
2026-01-31 21:13:13 +09:00
webchat: ["session:demo"],
},
},
},
// Custom model providers
models: {
mode: "merge",
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_KEY",
api: "openai-responses",
authHeader: true,
headers: { "X-Proxy-Region": "us-west" },
models: [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B",
api: "openai-responses",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
2026-01-31 21:13:13 +09:00
maxTokens: 32000,
},
],
},
},
},
// Cron jobs
cron: {
enabled: true,
2026-01-30 03:15:10 +01:00
store: "~/.openclaw/cron/cron.json",
2026-01-31 21:13:13 +09:00
maxConcurrentRuns: 2,
fix: unify session maintenance and cron run pruning (#13083) * fix: prune stale session entries, cap entry count, and rotate sessions.json The sessions.json file grows unbounded over time. Every heartbeat tick (default: 30m) triggers multiple full rewrites, and session keys from groups, threads, and DMs accumulate indefinitely with large embedded objects (skillsSnapshot, systemPromptReport). At >50MB the synchronous JSON parse blocks the event loop, causing Telegram webhook timeouts and effectively taking the bot down. Three mitigations, all running inside saveSessionStoreUnlocked() on every write: 1. Prune stale entries: remove entries with updatedAt older than 30 days (configurable via session.maintenance.pruneDays in openclaw.json) 2. Cap entry count: keep only the 500 most recently updated entries (configurable via session.maintenance.maxEntries). Entries without updatedAt are evicted first. 3. File rotation: if the existing sessions.json exceeds 10MB before a write, rename it to sessions.json.bak.{timestamp} and keep only the 3 most recent backups (configurable via session.maintenance.rotateBytes). All three thresholds are configurable under session.maintenance in openclaw.json with Zod validation. No env vars. Existing tests updated to use Date.now() instead of epoch-relative timestamps (1, 2, 3) that would be incorrectly pruned as stale. 27 new tests covering pruning, capping, rotation, and integration scenarios. * feat: auto-prune expired cron run sessions (#12289) Add TTL-based reaper for isolated cron run sessions that accumulate indefinitely in sessions.json. New config option: cron.sessionRetention: string | false (default: '24h') The reaper runs piggy-backed on the cron timer tick, self-throttled to sweep at most every 5 minutes. It removes session entries matching the pattern cron:<jobId>:run:<uuid> whose updatedAt + retention < now. Design follows the Kubernetes ttlSecondsAfterFinished pattern: - Sessions are persisted normally (observability/debugging) - A periodic reaper prunes expired entries - Configurable retention with sensible default - Set to false to disable pruning entirely Files changed: - src/config/types.cron.ts: Add sessionRetention to CronConfig - src/config/zod-schema.ts: Add Zod validation for sessionRetention - src/cron/session-reaper.ts: New reaper module (sweepCronRunSessions) - src/cron/session-reaper.test.ts: 12 tests covering all paths - src/cron/service/state.ts: Add cronConfig/sessionStorePath to deps - src/cron/service/timer.ts: Wire reaper into onTimer tick - src/gateway/server-cron.ts: Pass config and session store path to deps Closes #12289 * fix: sweep cron session stores per agent * docs: add changelog for session maintenance (#13083) (thanks @skyfallsin, @Glucksberg) * fix: add warn-only session maintenance mode * fix: warn-only maintenance defaults to active session * fix: deliver maintenance warnings to active session * docs: add session maintenance examples * fix: accept duration and size maintenance thresholds * refactor: share cron run session key check * fix: format issues and replace defaultRuntime.warn with console.warn --------- Co-authored-by: Pradeep Elankumaran <pradeepe@gmail.com> Co-authored-by: Glucksberg <markuscontasul@gmail.com> Co-authored-by: max <40643627+quotentiroler@users.noreply.github.com> Co-authored-by: quotentiroler <max.nussbaumer@maxhealth.tech>
2026-02-09 23:42:35 -05:00
sessionRetention: "24h",
},
// Webhooks
hooks: {
enabled: true,
path: "/hooks",
token: "shared-secret",
presets: ["gmail"],
2026-01-30 03:15:10 +01:00
transformsDir: "~/.openclaw/hooks",
mappings: [
{
id: "gmail-hook",
match: { path: "gmail" },
action: "agent",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}",
textTemplate: "{{messages[0].snippet}}",
deliver: true,
2026-01-13 07:15:57 +00:00
channel: "last",
to: "+15555550123",
thinking: "low",
timeoutSeconds: 300,
transform: {
module: "./transforms/gmail.js",
export: "transformGmail",
},
2026-01-31 21:13:13 +09:00
},
],
gmail: {
2026-01-30 03:15:10 +01:00
account: "openclaw@gmail.com",
label: "INBOX",
topic: "projects/<project-id>/topics/gog-gmail-watch",
subscription: "gog-gmail-watch-push",
pushToken: "shared-push-token",
hookUrl: "http://127.0.0.1:18789/hooks/gmail",
includeBody: true,
maxBytes: 20000,
renewEveryMinutes: 720,
serve: { bind: "127.0.0.1", port: 8788, path: "/" },
2026-01-31 21:13:13 +09:00
tailscale: { mode: "funnel", path: "/gmail-pubsub" },
},
},
// Gateway + networking
gateway: {
mode: "local",
port: 18789,
bind: "loopback",
2026-01-30 03:15:10 +01:00
controlUi: { enabled: true, basePath: "/openclaw" },
auth: {
mode: "token",
token: "gateway-token",
2026-01-31 21:13:13 +09:00
allowTailscale: true,
},
tailscale: { mode: "serve", resetOnExit: false },
remote: { url: "ws://gateway.tailnet:18789", token: "remote-token" },
2026-01-31 21:13:13 +09:00
reload: { mode: "hybrid", debounceMs: 300 },
},
skills: {
2026-01-15 04:07:29 +00:00
allowBundled: ["gemini", "peekaboo"],
load: {
2026-01-31 21:13:13 +09:00
extraDirs: ["~/Projects/agent-scripts/skills"],
},
install: {
preferBrew: true,
2026-01-31 21:13:13 +09:00
nodeManager: "npm",
},
entries: {
"nano-banana-pro": {
enabled: true,
apiKey: "GEMINI_KEY_HERE",
2026-01-31 21:13:13 +09:00
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
},
2026-01-31 21:13:13 +09:00
peekaboo: { enabled: true },
},
},
}
```
## Common patterns
### Multi-platform setup
2026-01-31 21:13:13 +09:00
```json5
{
2026-01-30 03:15:10 +01:00
agent: { workspace: "~/.openclaw/workspace" },
channels: {
whatsapp: { allowFrom: ["+15555550123"] },
telegram: {
enabled: true,
botToken: "YOUR_TOKEN",
2026-01-31 21:13:13 +09:00
allowFrom: ["123456789"],
},
discord: {
enabled: true,
token: "YOUR_TOKEN",
2026-01-31 21:13:13 +09:00
dm: { allowFrom: ["yourname"] },
},
},
}
```
### Secure DM mode (shared inbox / multi-user DMs)
If more than one person can DM your bot (multiple entries in `allowFrom`, pairing approvals for multiple people, or `dmPolicy: "open"`), enable **secure DM mode** so DMs from different senders dont share one context by default:
```json5
{
// Secure DM mode (recommended for multi-user or sensitive DM agents)
session: { dmScope: "per-channel-peer" },
channels: {
// Example: WhatsApp multi-user inbox
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15555550123", "+15555550124"],
},
// Example: Discord multi-user inbox
discord: {
enabled: true,
token: "YOUR_DISCORD_BOT_TOKEN",
dm: { enabled: true, allowFrom: ["alice", "bob"] },
},
},
}
```
### OAuth with API key failover
2026-01-31 21:13:13 +09:00
```json5
{
auth: {
profiles: {
"anthropic:subscription": {
provider: "anthropic",
mode: "oauth",
2026-01-31 21:13:13 +09:00
email: "me@example.com",
},
"anthropic:api": {
provider: "anthropic",
2026-01-31 21:13:13 +09:00
mode: "api_key",
},
},
order: {
2026-01-31 21:13:13 +09:00
anthropic: ["anthropic:subscription", "anthropic:api"],
},
},
agent: {
2026-01-30 03:15:10 +01:00
workspace: "~/.openclaw/workspace",
model: {
primary: "anthropic/claude-sonnet-4-5",
fallbacks: ["anthropic/claude-opus-4-6"],
2026-01-31 21:13:13 +09:00
},
},
}
```
### Anthropic subscription + API key, MiniMax fallback
2026-01-31 21:13:13 +09:00
```json5
{
auth: {
profiles: {
"anthropic:subscription": {
provider: "anthropic",
mode: "oauth",
2026-01-31 21:13:13 +09:00
email: "user@example.com",
},
"anthropic:api": {
provider: "anthropic",
2026-01-31 21:13:13 +09:00
mode: "api_key",
},
},
order: {
2026-01-31 21:13:13 +09:00
anthropic: ["anthropic:subscription", "anthropic:api"],
},
},
models: {
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
api: "anthropic-messages",
2026-01-31 21:13:13 +09:00
apiKey: "${MINIMAX_API_KEY}",
},
},
},
agent: {
2026-01-30 03:15:10 +01:00
workspace: "~/.openclaw/workspace",
model: {
primary: "anthropic/claude-opus-4-6",
2026-01-31 21:13:13 +09:00
fallbacks: ["minimax/MiniMax-M2.1"],
},
},
}
```
### Work bot (restricted access)
2026-01-31 21:13:13 +09:00
```json5
{
identity: {
name: "WorkBot",
2026-01-31 21:13:13 +09:00
theme: "professional assistant",
},
agent: {
2026-01-30 03:15:10 +01:00
workspace: "~/work-openclaw",
2026-01-31 21:13:13 +09:00
elevated: { enabled: false },
},
channels: {
slack: {
enabled: true,
botToken: "xoxb-...",
channels: {
"#engineering": { allow: true, requireMention: true },
2026-01-31 21:13:13 +09:00
"#general": { allow: true, requireMention: true },
},
},
},
}
```
### Local models only
2026-01-31 21:13:13 +09:00
```json5
{
agent: {
2026-01-30 03:15:10 +01:00
workspace: "~/.openclaw/workspace",
2026-01-31 21:13:13 +09:00
model: { primary: "lmstudio/minimax-m2.1-gs32" },
},
models: {
mode: "merge",
providers: {
lmstudio: {
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "lmstudio",
api: "openai-responses",
models: [
{
id: "minimax-m2.1-gs32",
name: "MiniMax M2.1 GS32",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 196608,
2026-01-31 21:13:13 +09:00
maxTokens: 8192,
},
],
},
},
},
}
```
## Tips
- If you set `dmPolicy: "open"`, the matching `allowFrom` list must include `"*"`.
- Provider IDs differ (phone numbers, user IDs, channel IDs). Use the provider docs to confirm the format.
- Optional sections to add later: `web`, `browser`, `ui`, `discovery`, `canvasHost`, `talk`, `signal`, `imessage`.
- See [Providers](/channels/whatsapp) and [Troubleshooting](/gateway/troubleshooting) for deeper setup notes.