Zalo Channel Setup and Capabilities for OpenClaw Bots
Covers the experimental Zalo channel in OpenClaw, including bundled plugin install, bot token setup, and verified direct message and group chat behavior. Useful for operators configuring Zalo bots.
Read this when
- Working on Zalo features or webhooks
Status: experimental. Both direct messages and group chats are implemented. Verified behavior on Zalo Bot Creator / Marketplace bots is shown in the Capabilities table below.
Bundled plugin
Zalo is included as a bundled plugin in current OpenClaw releases, so packaged builds require no separate install.
If you are on an older build, or a custom install that leaves Zalo out, install the npm package yourself:
- Install:
openclaw plugins install @openclaw/zalo - Pinned version:
openclaw plugins install @openclaw/zalo@<version>(pin only when you need reproducible installs) - From a local checkout:
openclaw plugins install ./path/to/local/zalo-plugin - Details: Plugins
Quick setup
- Get a bot token from https://bot.zaloplatforms.com (sign in, create a bot, configure settings). The token looks like
numeric_id:secret. On Marketplace bots, the runtime token you can actually use may show up in the bot's welcome message. - Provide the token, either through env
ZALO_BOT_TOKEN=...(default account only) or in config. - Verify
openclaw channels status --probe; if the Gateway is offline, start it. Config edits take effect via hot reload. Restart the Gateway when you have changed the service environment, so the new values load. - On the first DM contact, approve the pairing code (the default DM policy is pairing).
Minimal config:
{
channels: {
zalo: {
enabled: true,
accounts: {
default: {
botToken: "12345689:abc-xyz",
dmPolicy: "pairing",
},
},
},
},
}
Multi-account: place additional entries under channels.zalo.accounts.<id>, each carrying its own botToken/name. The flat channels.zalo.botToken form, with no accounts, is a legacy single-account shorthand. For new configs, accounts.<id>.* is preferred.
What it is
Zalo is a messaging app built for Vietnam. With its Bot API, the Gateway can run a bot across both 1:1 conversations and group chats. Routing back to Zalo is deterministic. Channel selection is never left to the model.
Covered here: Zalo Bot Creator / Marketplace bots. A separate product surface, Zalo Official Account (OA) bots, may behave differently, and is outside the scope of this page.
How it works
- Inbound messages land in the shared channel envelope, normalized, with media placeholders.
- Replies are always routed back to the same Zalo chat. Quote-reply stays off (
replyToModeis fixed off). - Long-polling (
getUpdates) is the default. Webhook mode can be enabled withchannels.zalo.webhookUrl. - In groups, an @mention is required to trigger the bot. Per-channel configuration of this is not possible.
Limits
| Limit | Value |
|---|---|
| Outbound text chunk size | 2000 characters (Zalo API limit) |
| Media size (inbound/outbound) | channels.zalo.mediaMaxMb, default 5 MB |
| Webhook request body | 1 MB, 30s read timeout |
| Webhook rate limit | 120 requests / 60s per path+client IP, then HTTP 429 |
| Webhook replay tombstones | 30 days, up to 20,000 completed events per account (keyed by message id) |
Access control
Direct messages
channels.zalo.dmPolicy:pairing(default) |allowlist|open|disabled.- Pairing: senders who are unknown receive a pairing code. Until approval, their messages are ignored. A code stays valid for 1 hour.
openclaw pairing list zaloopenclaw pairing approve zalo <CODE>- Details: Pairing
channels.zalo.allowFromtakes numeric Zalo user IDs (usernames cannot be looked up).openneeds"*".
Groups
The plugin supports group chats (chatTypes: ["direct", "group"]), controlled by mention plus group policy:
channels.zalo.groupPolicy:open|allowlist|disabled.channels.zalo.groupAllowFromlimits which sender IDs are allowed to trigger the bot in groups. When unset, it falls back toallowFrom.- Default resolution: with
channels.zaloconfigured, an unsetgroupPolicyresolves toopen. Ifchannels.zalois absent altogether, runtime fails closed toallowlist. - Reported real-world caveat: certain Marketplace-bot setups could not add the bot to a group at all. Should that happen to you, check your bot's Zalo Bot Platform settings. The constraint sits on the platform side, not in OpenClaw policy.
Long-polling vs webhook
- Default: long-polling (a public URL is not needed).
- Webhook mode: configure
channels.zalo.webhookUrlandchannels.zalo.webhookSecret.- The webhook URL has to be HTTPS.
- The webhook secret has to be between 8 and 256 characters.
- Events arrive from Zalo carrying an
X-Bot-Api-Secret-Tokenheader, which is verified using a constant-time comparison. - Webhook requests are served by the gateway HTTP at
channels.zalo.webhookPath(by default, the path taken from the webhook URL). - Requests have to use
Content-Type: application/json(or a+jsonmedia type). - OpenClaw sends HTTP 200 back only once the raw event has been durably stored. If storage fails, HTTP 500 is returned. The durable
200holdsx-openclaw-delivery-accepted: durable. Some reverse proxies may need that header to tell an OpenClaw acceptance apart from a generic200. It is left out of authentication, validation, and storage-error responses. - According to the Zalo API docs, getUpdates polling and webhook cannot be used at the same time.
Supported message types
- Text: fully supported, split into chunks of 2000 characters.
- Media: both inbound and outbound, limited by
mediaMaxMb. - Reactions, threads, polls, and native commands: the plugin does not support these.
- Streaming: block-streaming capability is declared by the plugin. Unlike certain other regional channels, Zalo offers no dedicated outbound queue or merge-text tuning knobs. If this matters for your use case, check the current behavior in your own environment.
Capabilities
| Feature | Status |
|---|---|
| Direct messages | Supported |
| Groups | Supported (mention-gated) |
| Media (inbound/outbound) | Supported, capped by mediaMaxMb |
| Reactions | Not supported |
| Threads | Not supported |
| Polls | Not supported |
| Native commands | Not supported |
| Reply-to / quote | Not used (fixed off) |
Delivery targets (CLI/cron)
Pass a chat ID as the target:
openclaw message send --channel zalo --target 123456789 --message "hi"
Troubleshooting
Bot does not respond:
- Inspect the token:
openclaw channels status --probe - Make sure the sender is approved (pairing or
allowFrom) - Look at the gateway logs:
openclaw logs --follow
Webhook not receiving events:
- Make sure the webhook URL is HTTPS
- Make sure the secret is 8-256 characters long
- Make sure the gateway HTTP endpoint can be reached on the path you configured
- Make sure getUpdates polling is not running as well (the two are mutually exclusive)
- A sudden burst of requests may produce HTTP 429 (120 requests / 60s per path+IP). Retry after backing off
Configuration reference
Full configuration: Configuration
| Setting | Description | Default |
|---|---|---|
channels.zalo.enabled | Turn channel startup on or off | true |
channels.zalo.accounts.<id>.botToken | Bot token issued by Zalo Bot Platform | - |
channels.zalo.accounts.<id>.tokenFile | Read the token from a file (symlinks are rejected) | - |
channels.zalo.accounts.<id>.name | Display name | - |
channels.zalo.accounts.<id>.enabled | Turn this account on or off | true |
channels.zalo.accounts.<id>.dmPolicy | DM policy for this account | pairing |
channels.zalo.accounts.<id>.allowFrom | DM allowlist (user IDs) | - |
channels.zalo.accounts.<id>.groupPolicy | Group policy for this account | see Groups |
channels.zalo.accounts.<id>.groupAllowFrom | Allowlist of group senders; falls back to allowFrom | - |
channels.zalo.accounts.<id>.mediaMaxMb | Inbound/outbound media cap (MB) | 5 |
channels.zalo.accounts.<id>.webhookUrl | Turn on webhook mode (HTTPS required) | - |
channels.zalo.accounts.<id>.webhookSecret | Webhook secret (8-256 chars) | - |
channels.zalo.accounts.<id>.webhookPath | Webhook path on the gateway HTTP server | webhook URL path |
channels.zalo.accounts.<id>.proxy | Proxy URL used for API requests | - |
channels.zalo.accounts.<id>.responsePrefix | Override for the outbound response prefix | - |
channels.zalo.defaultAccount | Account used by default when several are set up | default |
channels.zalo.botToken, channels.zalo.dmPolicy, and other flat top-level keys act as the legacy single-account shorthand for the settings listed above. Either form works.
Env option: ZALO_BOT_TOKEN=... resolves the token of the default account only.
Related
- Channels Overview - every channel that is supported
- Pairing - DM authentication and how pairing works
- Groups - group chat behavior and mention gating
- Channel routing - how messages are routed to sessions
- Security - the access model and hardening