Configuration secrets and channels
Configure credential references and native channels for Namespace Configuration resources. Secret values stay in the selected SecretDriver or documented service-account credential path.
Secret bindings
Use Configuration.secretBindings only to map a Namespace-owned OCC Secret to a
selected gateway environment variable. The referenced Secret must already belong
to the same Namespace as the Configuration and deploying Agent. The native
OpenClaw document in values then consumes that environment variable with its
normal env SecretRef:
{ "secretBindings": { "SLACK_BOT_TOKEN": { "source": { "kind": "secret", "namespaceId": "ns_123e4567-e89b-42d3-a456-426614174000", "id": "sec_123e4567-e89b-42d3-a456-426614174000" } } }}For example, native Slack configuration consumes SLACK_BOT_TOKEN with
{ "source": "env", "provider": "default", "id": "SLACK_BOT_TOKEN" }.
Supply model authentication through Agent harnessAuth,
not a Configuration binding.
Each binding value contains source.kind: "secret", the source
namespaceId, the source Secret id, and optional delivery.type: "env".
Omitting delivery normalizes to { "type": "env" }; no other delivery mode is
implemented. Binding names must be valid environment variable names and cannot
use reserved process-control prefixes such as OPENCLAW_, CODEX_, OCC_,
KUBERNETES_, OPENAI_, PATH, HOME, or proxy variables. Model
authentication destinations are reserved for harnessAuth.
OCC rejects cross-Namespace references and missing or foreign backend objects
even if IAM would otherwise allow the operation. When a Configuration write or Agent
provisioning submits bindings, a reserved or invalid destination or a Secret
reference to another Namespace fails with 400 INVALID_REQUEST and a message
naming the rule; so does an Agent harnessAuth source in another Namespace. A
reference to a Secret the Namespace does not hold fails with 404. Creating or updating a
Configuration whose resulting document contains bindings requires the normal
Configuration mutation permission and operate on every selected Secret,
including retained bindings when PATCH omits secretBindings. Creating or
updating an Agent assignment to a bound Configuration requires the normal Agent
mutation permission and operate on each exact Secret. Each check also requires
the selected SecretDriver to own the Secret: after the Installation selects
another SecretDriver, a write that keeps an old binding, and every create,
update, or deployment of an Agent assigned to that Configuration, fails with 503 and a
message naming the fix. Update the Configuration with replacement
secretBindings, or assign the Agent another Configuration. Namespace membership,
Configuration access, Agent access, or possession of a ref does not grant
consumption. Deployment stores normalized references and the selected
SecretDriver identity in the AgentRevision; it does not store backend
locators or value bytes. Secret storage CRUD, update/restart semantics,
Kubernetes Secret RBAC, no-leakage rules, and troubleshooting are owned by the
Kubernetes Secret Driver.
Secret boundaries
ConfigMaps are not secret storage. Use canonical inline SecretRefs and
secretBindings for gateway credentials. Use Agent harnessAuth for model
credentials. Never put plaintext values in Configuration values.
The Kubernetes and filesystem Configuration Drivers reject literal model API
keys, credential headers, and model credential environment values in their
known native fields before storage. The 400 names the field as a JSON pointer
within values, never the value. Unresolved references remain valid
Configuration data; deployment separately rejects model credential selectors
that compete with the Agent's binding.
Writes and deployment also require a ${NAME} reference or a SecretRef in the
other key and credential header slots the Gateway sends: MCP server headers
(also under nodeHost), memory.search.remote.apiKey and .headers (also
under agents.entries.<id>), every tools.media request (models[] and each
capability's defaults: headers, request.headers, request.auth, the TLS
client keys and credential-named providerOptions, also in the per-capability
models lists older releases read), speech (tts, personas, per-Agent and
voice blocks) and Talk provider apiKeys, tools.web.fetch.headers,
diagnostics.otel.headers, gateway.remote.edgeAuth,
skills.entries.<id>.apiKey and cron.webhookToken. Headers follow the model
provider's credential name rule, and a name that is not an HTTP field name is
refused.
The other slots OpenClaw marks sensitive need one too (${NAME} works in every
slot, a SecretRef where OpenClaw takes one):
gateway.auth.token, gateway.remote.token and .password,
gateway.controlUi.github.token, hooks.token, hooks.gmail.pushToken, sandbox
SSH identityData, certificateData and knownHostsData (under
agents.defaults and agents.entries.<id>), and
storage.locations.<name>.encryption.passphrase. Under channels.<id> and
plugins.entries.<id>.config, a string whose key OpenClaw treats as secret needs
one: a key ending in token, or naming a password, secret, API key, encryption
or private key, or service account, such as botToken or webSearch.apiKey. A
key naming where a credential is read from (ending in File, Path, Dir or
Env) is not secret. Fields bundled manifests mark sensitive count too
(telnyx.publicKey, Synology Chat incomingUrl and webhookUrl), and a
headers map there follows the header rule. Environment maps keep their own
rules, and a hook mapping's sessionKey, a routing key, stays as written. A
literal gateway.auth.password is refused on Configuration and Preset saves.
A plugin secret input that OpenClaw resolves (a bundled manifest's secret inputs,
such as Codex appServer.authToken and appServer.headers, and web search and
fetch apiKeys) also takes a whole $NAME value: OpenClaw reads it as an
environment SecretRef. The other slots refuse a $NAME value.
A stored document that predates one of these rules still reads, so its operator can replace it.
OpenClaw owns SecretRef syntax, provider configuration, and validation. OCC,
ConfigurationDriver, and Kubernetes Compute preserve native env, file, and
exec SecretRefs as unresolved JSON. The selected SecretDriver only stores OCC
Secret values and resolves approved env delivery metadata for the owning
gateway. With the OpenShell Credential Gateway, an Agent's model key can instead
be a credential source that the Sandbox proxy
substitutes, for dedicated Codex only. That path does not apply to Configuration
bindings. A general Secret Broker, value history, and automatic rotation remain
unimplemented.
Native channel configuration
Configure channels directly in the Agent's complete native OpenClaw
Configuration. Slack is the channel with live Enterprise integration coverage.
The Kubernetes Compute Driver recognizes enabled slack and msteams
configuration shapes; unknown enabled providers fail closed. Teams requires
operator-provided ingress and remains unverified end to end.
channels.defaults and channels.modelByChannel are shared settings, not
providers. The recognized native channel configurations consume these
gateway-only credential values:
| Provider | Gateway Secret keys |
|---|---|
slack |
SLACK_APP_TOKEN and SLACK_BOT_TOKEN for the default account; account-specific keys for named accounts |
msteams |
MSTEAMS_APP_PASSWORD |
On the bundled Kubernetes Compute, every ${NAME} reference and environment
SecretRef in an enabled native channel needs a matching secretBindings entry,
including a reference inside other text (xoxb-${NAME}). Outside credential
fields a reference with a fallback (${NAME:-text}) needs none: the fallback
fills the unset name. $${NAME} is literal text, not a reference.
A channel secret-input field (Slack appToken, botToken, signingSecret,
userToken and relay.authToken, on the channel or a named account; Teams
appPassword on the channel) has stricter rules:
- A Slack
appTokenorbotTokenwritten as an object must be an env SecretRef with providerdefault: the Slack Channel Driver reads no other SecretRef or object. The rule covers the tokens that Driver checks, so it skips the app token in HTTP mode and disabled accounts. - A reference with a fallback needs a binding: this Compute sets only bound names, so the fallback, often empty, would replace the credential.
- OpenClaw reads a whole
$NAMEvalue as an environment SecretRef, so it needs a binding too. - OpenClaw substitutes only uppercase names, so any other
${, such as${slack_bot}or${NAME-text}, is refused: OpenClaw would send that text as the credential. The message quotes no other text of the value than a lowercase${name}or the name before-. - The Configuration Driver accepts a credential field only as exactly one
${NAME}or a SecretRef, so even with a binding it refuses a fallback, a$NAMEvalue, or a reference with other text, spaces or references. The message names the form.
Configuration create and update and Agent provisioning refuse these values with
400 INVALID_REQUEST. The message names the setting, and also the binding when
one is missing, so send the values and their bindings in one request.
Deployment refuses a stored Configuration that predates this rule with the same
message. Disabled channel provider blocks do not require
bindings.
Add a native default Slack account with environment SecretRefs:
{ "channels": { "slack": { "enabled": true, "mode": "socket", "replyToModeByChatType": { "channel": "all" }, "appToken": { "source": "env", "provider": "default", "id": "SLACK_APP_TOKEN" }, "botToken": { "source": "env", "provider": "default", "id": "SLACK_BOT_TOKEN" }, "dmPolicy": "allowlist", "allowFrom": ["U0123456789"], "channels": { "C0123456789": { "requireMention": true } } } }}Give each named account under channels.slack.accounts its own environment
names, such as SLACK_WORK_APP_TOKEN and SLACK_WORK_BOT_TOKEN, with a
matching secretBindings entry for each:
{ "channels": { "slack": { "enabled": true, "mode": "socket", "accounts": { "work": { "appToken": { "source": "env", "provider": "default", "id": "SLACK_WORK_APP_TOKEN" }, "botToken": { "source": "env", "provider": "default", "id": "SLACK_WORK_BOT_TOKEN" }, "dmPolicy": "allowlist", "allowFrom": ["U0123456789"] } } } }}With named accounts, remove any SLACK_APP_TOKEN and SLACK_BOT_TOKEN
bindings unless you also want a top-level default account (or name an account
default). OpenClaw reads those two names from the gateway environment as an
implicit default account. That account opens a second Socket Mode connection,
receives a share of the Slack app's events, and applies the top-level
channels.slack policy (dmPolicy, allowFrom) instead of the named account's
restrictions. The console's Slack editor handles only the default account; for
named accounts its Credentials tab checks the keys the document references, and
you bind them through the Configuration API.
The console's new Slack setup and bundled Slack Presets set
channels.slack.replyToModeByChatType.channel: "all", which threads channel
replies without changing DM or group-DM reply behavior. Existing Slack blocks
keep their saved settings, including omissions and overrides. To disable the
channel default, set replyToModeByChatType.channel: "off" in native
Configuration JSON; per-channel overrides still take precedence. The API stores
the supplied native document unchanged, so API clients should include the
chat-type setting shown above. Save and redeploy to apply a change.
The console exposes Direct-message policy and Allowed people in direct messages
separately from channel access. New setup selects allowlist and requires sender
IDs before saving; choose disabled for channel-only access. pairing admits
approved senders, with optional preapproved IDs. Selecting open writes
allowFrom: ["*"]; switching to Allowlist or Pairing clears that wildcard in
the editor. Disabled preserves the saved sender list for later use. Existing
omitted policies remain omitted until explicitly selected. Organization-wide
installs support Disabled or Open when DMs are enabled; Disabled is recommended.
The editor preserves enterpriseOrgInstall and dm.enabled; a native
dm.enabled: false continues to block DMs regardless of the selected policy.
Direct-message sessions
When the Configuration enables a channel and leaves session.dmScope unset,
deployment sets it to "per-account-channel-peer". An enabled channel is any
channels entry except defaults and modelByChannel whose enabled is not
false. Each direct-message sender then gets a separate session for each
channel and channel account, including named channels.slack.accounts entries.
OpenClaw's own default, "main", puts every sender in the main session that
browser chat opens and turns on recall across conversations, so a sender could
get the Agent to repeat members' chat content. With the OCE default, that
recall stays off unless memory.search.rememberAcrossConversations turns it on.
An explicit value, including "main", is kept.
An Agent without an enabled channel gets no session.dmScope, so OpenClaw's
"main" and its recall across conversations stay. When you enable a channel
later, the next deployment sets the default.
After the next deployment of an Agent with a channel that used "main" by
default, each direct-message sender starts a new session without the earlier
shared context. The main session keeps its history, and new browser chat
sessions are no longer grouped under it. Members can still read
channel conversations, as the native admin pilot exception
describes.
Microsoft Teams uses the native msteams provider identifier. Its application
and tenant identifiers are ordinary nonsecret configuration strings; only the
application password is an environment SecretRef:
{ "channels": { "msteams": { "enabled": true, "appId": "00000000-0000-0000-0000-000000000000", "tenantId": "11111111-1111-1111-1111-111111111111", "appPassword": { "source": "env", "provider": "default", "id": "MSTEAMS_APP_PASSWORD" } } }}For Slack setup, set the Agent's executionMode to dedicated; embedded mode
is rejected because its combined gateway/Agent cannot isolate channel
credentials. Preserve existing Codex/model settings and enable the native Slack
plugin. Explicitly redeploy the Agent to snapshot the updated document. Store
each channel value as a Namespace Secret and bind its exact reference to the
native environment destination with secretBindings. Grant the consuming
Agent's returned servicePrincipalId access through the
Namespace IAM APIs. Only its
dedicated gateway receives these admitted bindings; generated runtime
credentials contain no channel tokens. See
Kubernetes credential ownership.
Teams message ingress requires a separately deployed and reviewed public Bot
Framework /api/messages webhook. Enterprise does not provide that webhook;
end-to-end Teams behavior remains unverified. See Slack testing
for channel integration coverage.
