OpenClaw EnterpriseDOCSGitHub

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:

json
{  "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:

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:

json
{  "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:

json
{  "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:

json
{  "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.

Search documentation