QQ Bot Channel Setup, Configuration, and Usage for OpenClaw

Covers connecting OpenClaw to the official QQ Bot API, creating a bot on the QQ Open Platform, and adding the channel. Includes chat type and media support limits for C2C, group, and guild messages.

Read this when

  • You want to connect OpenClaw to QQ
  • You need QQ Bot credential setup
  • You want QQ Bot group or private chat support

QQ Bot reaches OpenClaw through the official QQ Bot API (a WebSocket gateway). The main chat types are C2C private chat and group @-mentions, and both carry rich media (images, voice, video, files). Guild channel messages support text and remote-URL images only; voice, video, file uploads, and local/Base64 images are unavailable in guild channels. Reactions and threads are unsupported everywhere.

Status: official downloadable plugin.

Install

openclaw plugins install @tencent-connect/openclaw-qqbot

When the bot was installed as @openclaw/qqbot under plugin id qqbot, openclaw plugins update qqbot and openclaw update rewrite it to @tencent-connect/openclaw-qqbot under plugin id openclaw-qqbot. Channel config stays under channels.qqbot.

Setup

  1. Open the QQ Open Platform, then scan the QR code with your phone QQ to register or log in.
  2. Click Create Bot to create a new QQ bot.
  3. On the bot's settings page, locate AppID and AppSecret and copy them.

Note

Copy the AppSecret before you leave the QQ Open Platform page. If you don't, you will have to regenerate it.

  1. Add the channel:
openclaw channels add --channel qqbot --token "AppID:AppSecret"
  1. Check openclaw channels status --probe; if the Gateway is offline, start it. Config changes follow hot reload.

Inbound durability

For QQ gateway turn events, OpenClaw persists the raw event before advancing the saved gateway resume sequence. Pending or retryable turns survive a Gateway restart, remain serialized per conversation, and use the provider event ID to suppress duplicate queue entries while the active or retained completion record exists.

If durable admission fails, OpenClaw terminates the current gateway socket without advancing the sequence. The reconnect/resume path can then request the uncommitted event again. Delivery is still at least once across the queue-to-agent boundary, so a crash during handoff can replay a turn.

Interactive setup:

openclaw channels add

As an alternative to typing AppID/AppSecret by hand, the wizard also supports QR-code binding: scan the code with the phone app tied to the target QQ Bot and binding completes. OpenClaw persists the returned credentials under the account's config scope.

Configure

Minimal config:

{
  channels: {
    qqbot: {
      enabled: true,
      dmPolicy: "open",
      allowFrom: ["openclaw:approval-disabled"],
      appId: "YOUR_APP_ID",
      clientSecret: "YOUR_APP_SECRET",
    },
  },
}

These examples keep direct messages open with dmPolicy: "open" while the openclaw:approval-disabled marker leaves native approval actions disabled. To enable those actions, replace the marker with specific approver QQ OpenIDs.

Default-account env vars (top-level account only):

  • QQBOT_APP_ID
  • QQBOT_CLIENT_SECRET

Use a SecretRef to keep the AppSecret out of the channel config. For an environment-backed secret:

{
  channels: {
    qqbot: {
      enabled: true,
      dmPolicy: "open",
      allowFrom: ["openclaw:approval-disabled"],
      appId: "YOUR_APP_ID",
      clientSecret: {
        source: "env",
        provider: "default",
        id: "QQBOT_CLIENT_SECRET",
      },
    },
  },
}

For a file-backed AppSecret, configure a singleValue file provider:

{
  secrets: {
    providers: {
      qqbot_secret: {
        source: "file",
        path: "/path/to/qqbot-secret.txt",
        mode: "singleValue",
      },
    },
  },
  channels: {
    qqbot: {
      enabled: true,
      dmPolicy: "open",
      allowFrom: ["openclaw:approval-disabled"],
      appId: "YOUR_APP_ID",
      clientSecret: { source: "file", provider: "qqbot_secret", id: "value" },
    },
  },
}

Notes:

  • openclaw channels add --channel qqbot --token-file ... sets the AppSecret only; appId must already be set in config or QQBOT_APP_ID. Run openclaw doctor --fix afterward to migrate the legacy file setting.
  • clientSecret accepts plaintext or an environment-, file-, exec-, or store-backed SecretRef. OpenClaw resolves the reference before handing the credential to the QQ Bot plugin.
  • clientSecretFile is a migration-only legacy setting. openclaw doctor --fix replaces it with a file-backed clientSecret SecretRef. New configurations should use clientSecret directly.
  • Top-level credentials and environment fallbacks belong only to the default account. Named accounts configure their own appId and clientSecret.

Streaming

{
  channels: {
    qqbot: {
      streaming: {
        mode: "partial", // block streaming: "partial" (default) or "off"
        nativeTransport: true, // use QQ's official C2C stream_messages API for DMs
      },
    },
  },
}
  • streaming.mode: "off" disables block streaming for the account.
  • streaming.nativeTransport: true streams C2C (DM) replies through QQ's official stream_messages API; group/channel targets are unaffected.
  • Legacy streaming: true|false scalars and the streaming.c2cStreamApi key migrate to this shape via openclaw doctor --fix.
  • /bot-streaming on|off toggles the same config from a DM.

Access policy

  • allowFrom / groupAllowFrom determine who is allowed to talk to the bot in C2C / group contexts. The enforcement mode is governed by dmPolicy / groupPolicy (open | allowlist | disabled). Once allowFrom contains a concrete (non-wildcard) entry, dmPolicy takes the value allowlist; if not, open applies. Likewise, groupPolicy resolves to allowlist when either groupAllowFrom or allowFrom holds a concrete entry, and to open otherwise.
  • Quoted-message text that QQ delivers as supplemental context is governed by contextVisibility. With the default, "all", quoted text is kept as received. Choose "allowlist" so that quoted bodies are included only when the quoted sender satisfies the configured sender policy, or choose "allowlist_quote" to retain explicit quotes while filtering out other supplemental context. Refer to Groups.
  • No matter what dmPolicy / groupPolicy say, "Auth: allowlist" slash commands need an explicit non-wildcard entry in allowFrom (or in groupAllowFrom for group invocations). See Slash commands.

Multi-account setup

Several QQ bots can run inside one OpenClaw instance:

{
  channels: {
    qqbot: {
      enabled: true,
      dmPolicy: "open",
      allowFrom: ["openclaw:approval-disabled"],
      appId: "111111111",
      clientSecret: { source: "env", provider: "default", id: "QQBOT_DEFAULT_SECRET" },
      accounts: {
        bot2: {
          enabled: true,
          dmPolicy: "open",
          allowFrom: ["openclaw:approval-disabled"],
          appId: "222222222",
          clientSecret: { source: "env", provider: "default", id: "QQBOT_BOT2_SECRET" },
        },
      },
    },
  },
}

Keyed by appId, every account gets its own WebSocket connection, API client, and token cache. Each log line carries the id of the account that produced it, so diagnostics remain distinguishable when multiple bots share one Gateway.

During startup, an account whose SecretRef value is missing simply becomes unavailable; its healthy siblings and the Gateway keep running. A reference that explicitly fails is never replaced by environment credentials or plugin backup credentials. Startup or reload still fails for malformed refs and unknown secret providers. After a reload, an account whose configuration did not change may keep its last-known-good credential; refer to Secrets runtime model.

To add a second bot from the CLI:

openclaw channels add --channel qqbot --account bot2 --token "222222222:secret-of-bot-2"

Group chats

Group support is keyed on QQ group OpenIDs, not display names. After adding the bot to a group, either mention it or set the group up to run without a mention.

{
  channels: {
    qqbot: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["member_openid"],
      groups: {
        "*": {
          requireMention: true,
          commandLevel: "all",
          historyLimit: 50,
          tools: { deny: ["exec", "read", "write"] },
        },
        GROUP_OPENID: {
          name: "Release room",
          requireMention: false,
          ignoreOtherMentions: true,
          commandLevel: "safety",
          historyLimit: 20,
          prompt: "Keep replies short and operational.",
        },
      },
    },
  },
}

Defaults applying to all groups come from groups["*"]; an entry for one specific group in groups.GROUP_OPENID takes precedence over those defaults. Group settings:

FieldDefaultDescription
requireMentiontrueAn @-mention is needed before the bot will reply.
commandLevelallThe built-in slash commands permitted in the group (see below).
ignoreOtherMentionsfalseMessages that mention someone other than the bot are dropped.
historyLimit50Non-mention messages retained as context for the next turn that mentions the bot. History is turned off by 0.
tools,Tool allow/deny rules covering the entire group.
toolsBySender,Tool overrides for individual senders; see Groups.
nameopenid prefixHuman-friendly label shown in logs and group context.
promptbuilt-in defaultBehavior prompt for one group, appended to the agent context.

Accepted values for commandLevel:

LevelBehavior
allBuilt-in commands that already exist remain usable. A few are no longer listed in menus, yet authorized users can still execute them inside the group.
safety/help, /btw, /stop remain visible in the group; commands that are sensitive (/config, /tools, /bash, and similar) have to be issued in private chat.
strictOnly the group-session controls required for strict operation are permitted. /stop continues to work, letting an authorized sender interrupt a run that is in progress.

Legacy QQBot toolPolicy entries are no longer supported. To migrate them to tools, run openclaw doctor --fix.

The activation modes are mention and always. requireMention: true is mapped to mention; requireMention: false is mapped to always. When a session-level activation override is set, it takes priority over config.

Queues for inbound messages are kept per peer. Group peers receive a bigger cap (50, versus 20 for direct peers), drop bot-authored messages ahead of human ones once full, and coalesce bursts of ordinary group messages into a single attributed turn. Slash commands execute sequentially, separately from any merge batch.

Voice (STT / TTS)

Two-level configuration applies to STT and TTS, with fallback by priority:

SettingPlugin-specificFramework fallback
STTchannels.qqbot.sttthe first tools.media.models[] entry that supports audio
TTSchannels.qqbot.tts, channels.qqbot.accounts.<id>.ttstts
{
  channels: {
    qqbot: {
      stt: {
        provider: "your-provider",
        model: "your-stt-model",
      },
      tts: {
        provider: "your-provider",
        model: "your-tts-model",
        voice: "your-voice",
      },
      accounts: {
        "qq-main": {
          tts: {
            providers: {
              openai: { voice: "shimmer" },
            },
          },
        },
      },
    },
  },
}

Setting enabled: false on either one disables it. Account-level TTS overrides share the same shape as tts and are deep-merged on top of channel/global TTS config.

By default, STT requests time out after 60 seconds. Plugin-specific STT relies on the selected models.providers.<id>.timeoutSeconds override. Framework audio STT relies on the timeoutSeconds of the selected audio-capable tools.media.models[] entry, then on the selected provider override.

Agents see inbound QQ voice attachments as audio media metadata, while raw voice files are kept out of generic MediaPaths. When TTS is configured, [[audio_as_voice]] inside a plain-text reply synthesizes TTS and sends a native QQ voice message.

Outbound audio upload and transcode behavior is also tunable through channels.qqbot.audioFormatPolicy:

  • sttDirectFormats
  • uploadDirectFormats
  • transcodeEnabled

Target formats

FormatDescription
qqbot:c2c:OPENIDPrivate chat (C2C)
qqbot:group:GROUP_OPENIDGroup chat
qqbot:channel:CHANNEL_IDGuild channel

Note

User OpenIDs are unique to each bot. An OpenID that Bot A received cannot be used to send messages through Bot B.

Slash commands

Built-in commands that are intercepted ahead of the AI queue:

CommandAuthScopeDescription
/bot-ping,anyLatency test
/bot-help,anyList all commands
/bot-me,private onlyShow the sender's QQ user ID (openid) for allowFrom / groupAllowFrom setup
/bot-version,private onlyShow the OpenClaw framework version and plugin version
/bot-upgrade,private onlyShow the QQBot upgrade guide link
/bot-approveallowlistprivate onlyManage command-execution approval config (on / off / always / reset / status)
/bot-logsallowlistprivate onlyExport recent gateway logs as a file
/bot-clear-storageallowlistprivate onlyDelete cached downloads under the QQBot media directory
/bot-streamingallowlistprivate onlyToggle C2C streaming replies
/bot-group-allwaysallowlistprivate onlyToggle the default group activation mode (mention-required vs. always-on)

Add ? to any command to get usage help (for example, /bot-upgrade ?).

Commands marked "Auth: allowlist" also demand that the sender's openid appear in an explicit allowFrom list without a wildcard (for commands issued in a group, groupAllowFrom wins, with allowFrom as the fallback). A wildcard allowFrom: ["*"] allows chatting but not these commands. If one of them is run outside private chat, or by someone unauthorized, the caller gets a hint instead of the message being dropped silently.

/bot-me, /bot-version and /bot-upgrade work only in private chat and need no allowlist entry: any C2C sender is allowed to run them.

Where QQ Bot exec approvals rely on the default same-chat fallback, clicking the native approval buttons is governed by the same explicit command allowlist, with no wildcard support. Configuring channels.qqbot.execApprovals.approvers grants approval-only access while leaving broader command access closed. Native exec approvals are on by default.

Media and storage

  • Media moving inbound, outbound, and across the gateway bridge all share a single payload root beneath ~/.openclaw/media/qqbot (with OPENCLAW_HOME respected when it is set), which keeps uploads, downloads, and transcode caches inside one guarded directory.
  • Delivery of rich media to C2C and group targets runs through one sendMedia path. Local files and in-memory buffers at or above 5 MiB go to QQ's chunked upload endpoints, while smaller payloads and remote-URL or Base64 sources go to the one-shot upload API.
  • Should a hot upgrade cut off the Gateway while it is still writing openclaw.json, the plugin pulls the last-known appId / clientSecret for that account from an internal snapshot at the next start. An intentional config change is never overwritten this way, and the QR code does not need to be scanned again.

Troubleshooting

  • Gateway does not start / no inbound messages: confirm that appId and clientSecret are correct and that the bot is enabled on the QQ Open Platform. A missing credential appears as "QQBot not configured (missing appId or clientSecret)".
  • Setup with --token-file still shows unconfigured: --token-file sets the AppSecret only. appId still has to be set in config or in QQBOT_APP_ID.
  • Bursty group replies collide: when a peer's queue fills, the inbound queue drops bot-authored messages before human ones, and bursts of ordinary (non-command) group messages are merged into a single attributed turn, so bot chatter flooding in should not starve human messages.
  • Proactive messages not arriving: QQ may block messages a bot initiates when the user has not interacted recently.
  • Voice not transcribed: verify STT is configured and that the provider can be reached.
2,371 words · updated Oct 25, 2026