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
- Open the QQ Open Platform, then scan the QR code with your phone QQ to register or log in.
- Click Create Bot to create a new QQ bot.
- 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.
- Add the channel:
openclaw channels add --channel qqbot --token "AppID:AppSecret"
- 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_IDQQBOT_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;appIdmust already be set in config orQQBOT_APP_ID. Runopenclaw doctor --fixafterward to migrate the legacy file setting.clientSecretaccepts plaintext or an environment-, file-, exec-, or store-backed SecretRef. OpenClaw resolves the reference before handing the credential to the QQ Bot plugin.clientSecretFileis a migration-only legacy setting.openclaw doctor --fixreplaces it with a file-backedclientSecretSecretRef. New configurations should useclientSecretdirectly.- Top-level credentials and environment fallbacks belong only to the default
account. Named accounts configure their own
appIdandclientSecret.
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: truestreams C2C (DM) replies through QQ's officialstream_messagesAPI; group/channel targets are unaffected.- Legacy
streaming: true|falsescalars and thestreaming.c2cStreamApikey migrate to this shape viaopenclaw doctor --fix. /bot-streaming on|offtoggles the same config from a DM.
Access policy
allowFrom/groupAllowFromdetermine who is allowed to talk to the bot in C2C / group contexts. The enforcement mode is governed bydmPolicy/groupPolicy(open|allowlist|disabled). OnceallowFromcontains a concrete (non-wildcard) entry,dmPolicytakes the valueallowlist; if not,openapplies. Likewise,groupPolicyresolves toallowlistwhen eithergroupAllowFromorallowFromholds a concrete entry, and toopenotherwise.- 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/groupPolicysay, "Auth: allowlist" slash commands need an explicit non-wildcard entry inallowFrom(or ingroupAllowFromfor 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:
| Field | Default | Description |
|---|---|---|
requireMention | true | An @-mention is needed before the bot will reply. |
commandLevel | all | The built-in slash commands permitted in the group (see below). |
ignoreOtherMentions | false | Messages that mention someone other than the bot are dropped. |
historyLimit | 50 | Non-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. |
name | openid prefix | Human-friendly label shown in logs and group context. |
prompt | built-in default | Behavior prompt for one group, appended to the agent context. |
Accepted values for commandLevel:
| Level | Behavior |
|---|---|
all | Built-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. |
strict | Only 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:
| Setting | Plugin-specific | Framework fallback |
|---|---|---|
| STT | channels.qqbot.stt | the first tools.media.models[] entry that supports audio |
| TTS | channels.qqbot.tts, channels.qqbot.accounts.<id>.tts | tts |
{
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:
sttDirectFormatsuploadDirectFormatstranscodeEnabled
Target formats
| Format | Description |
|---|---|
qqbot:c2c:OPENID | Private chat (C2C) |
qqbot:group:GROUP_OPENID | Group chat |
qqbot:channel:CHANNEL_ID | Guild 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:
| Command | Auth | Scope | Description |
|---|---|---|---|
/bot-ping | , | any | Latency test |
/bot-help | , | any | List all commands |
/bot-me | , | private only | Show the sender's QQ user ID (openid) for allowFrom / groupAllowFrom setup |
/bot-version | , | private only | Show the OpenClaw framework version and plugin version |
/bot-upgrade | , | private only | Show the QQBot upgrade guide link |
/bot-approve | allowlist | private only | Manage command-execution approval config (on / off / always / reset / status) |
/bot-logs | allowlist | private only | Export recent gateway logs as a file |
/bot-clear-storage | allowlist | private only | Delete cached downloads under the QQBot media directory |
/bot-streaming | allowlist | private only | Toggle C2C streaming replies |
/bot-group-allways | allowlist | private only | Toggle 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(withOPENCLAW_HOMErespected 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
sendMediapath. 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-knownappId/clientSecretfor 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
appIdandclientSecretare 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-filestill shows unconfigured:--token-filesets the AppSecret only.appIdstill has to be set in config or inQQBOT_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.