安装与下载
1 ClawHub CLI
从源直接安装技能的最快方式。
npx clawhub@latest install ent-zalo-bridge
2 手动安装
将技能文件夹复制到以下位置之一
~/.openclaw/skills/
{'/skills/'}
优先级:工作区 › 全局 › 内置
3 提示词安装
将此提示词复制到 Openclaw 即可自动安装。
请帮我使用 Clawhub 安装 skill-ent-zalo-bridge。如果尚未安装 Clawhub,请先安装(npm i -g clawhub)。
更倾向于下载?
获取包含原生技能文件的ZIP压缩包。
基本信息
ent-zalo-bridge
安装指南
- ClawHub CLI
从源直接安装技能的最快方式。
npx clawhub@latest install ent-zalo-bridge - 手动安装
将技能文件夹复制到以下位置之一
全局模式
~/.openclaw/skills/
工作区
/skills/
优先级:工作区 > 本地 > 内置 - 提示词安装
将此提示词复制到 OpenClaw 即可自动安装。
请帮我使用 Clawhub 安装 ent-zalo-bridge。如果尚未安装 Clawhub,请先安装(npm i -g clawhub)。
更倾向于下载?
获取包含原生技能文件的ZIP压缩包。
下载技能 (.zip)
什么是
The Zalo Bot Webhook Bridge is an OpenClaw skill for deploying a lightweight, dependency-free Node.js webhook service that receives messages from the Zalo Bot Platform. It configures bot credentials, validates webhook requests, records incoming events, exposes health endpoints, and registers the public HTTPS webhook URL with Zalo.
Designed for secure and reliable automation, the bridge listens only on 127.0.0.1:8787 and uses Caddy, Nginx, or a secure tunnel to provide public HTTPS access. As part of Openclaw Skills, it supports continuous operation through systemd while preserving clear boundaries around secrets, webhook ownership, and existing bot data.
应用场景
Deploy a Zalo Bot message receiver for OpenClaw.
Connect a public HTTPS domain or tunnel to a local webhook service.
Automatically register a Zalo webhook with the setWebhook API.
Capture incoming Zalo events for debugging, auditing, or downstream automation.
Run the bridge continuously on a VPS or Linux host with systemd.
Validate bot credentials before modifying webhook configuration.
Combine inbound webhook handling with a separate Zalo message-sending skill for two-way communication.
Diagnose webhook delivery, secret mismatches, incorrect paths, service failures, and post-reboot downtime.
工作原理
The agent identifies a request to install or deploy a Zalo Bot bridge and extracts the bot token, optional secret, and public HTTPS domain.
It verifies the bot token with the Zalo getMe endpoint and stops when required credentials or the domain are missing.
It creates the dependency-free Node.js webhook service and configures ZALO_BOT_TOKEN, ZALO_SECRET, and PORT.
The service binds to 127.0.0.1:8787, provides health endpoints, and accepts GET or POST requests at /zalo/webhook and /webhook.
Incoming POST bodies are size-limited to 2 MB, parsed when valid JSON is available, logged as structured events, and saved as the latest event payload.
A reverse proxy or tunnel exposes the local service through a public HTTPS URL such as https:///zalo/webhook.
The agent calls setWebhook with the public URL and configured secret token, then checks the endpoint and webhook status.
A test message is sent to the bot or a Zalo group, and logs/events.ndjson is inspected to confirm delivery.
The service is managed by systemd with automatic restart support so the bridge remains available after failures and reboots.
配置指南
Prerequisites
Node.js 18 or newer.
A valid ZALO_BOT_TOKEN in the format :.
A public HTTPS domain or tunnel that reaches the local service.
Port 8787 available locally, or a custom PORT value.
Owner authorization before handling bot credentials or changing webhook settings.
Verify the bot token
curl -s "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/getMe"
Create and configure the service
Place the webhook implementation at zalo-bridge/zalo_webhook.js, then configure the environment:
export ZALO_BOT_TOKEN=''
export ZALO_SECRET=''
export PORT=8787
node zalo-bridge/zalo_webhook.js
The service should remain bound to 127.0.0.1:8787; do not expose the Node.js listener directly to the internet.
Register the webhook
After configuring HTTPS routing and confirming the health endpoint, register the webhook:
curl -s -X POST "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/setWebhook"
-H "Content-Type: application/json"
-d "{"url":"https:///zalo/webhook","secret_token":"$ZALO_SECRET"}"
Confirm the public route before registration:
curl -s https:///zalo/health
Run continuously with systemd
Create /etc/systemd/system/zalo-bridge.service with the Node.js path, working directory, environment variables, and a non-root user. Then enable it:
sudo systemctl daemon-reload
sudo systemctl enable --now zalo-bridge
sudo systemctl status zalo-bridge
Configure HTTPS routing
Use Caddy or Nginx to proxy /zalo/* to 127.0.0.1:8787, or use a tunnel such as cloudflared, ngrok, or tino.page. The URL supplied to Zalo must be publicly reachable over HTTPS and must preserve the /zalo/webhook path.
Zalo Bot Webhook Bridge for OpenClaw 数据架构与分类体系
Environment variables
Variable Purpose Default or requirement
ZALO_BOT_TOKEN Token used to call Zalo Bot API endpoints Required
ZALO_SECRET Secret token used for webhook request validation Recommended
PORT Local HTTP listener port 8787
Generated files
Path Format Contents
logs/events.ndjson Newline-delimited JSON Timestamped service, warning, error, and webhook event records
logs/last_event.json Pretty-printed JSON Most recently parsed webhook payload
zalo-bridge/zalo_webhook.js JavaScript Dependency-free Node.js HTTP service
/etc/systemd/system/zalo-bridge.service systemd unit 24/7 process management and automatic restart configuration
Event record taxonomy
Each log line includes a timestamp field t, a severity field level, and a message field msg. Webhook records may also include secretPresent and the parsed payload; service startup records include the listening port and whether token or secret values are configured. Typical messages include zalo-bridge listening, webhook received, secret mismatch, and webhook error.
HTTP routes
GET /, /health, /zalo, and /zalo/health: Return a JSON health response.
GET /zalo/webhook and /webhook: Return a challenge or ok response.
POST /zalo/webhook and /webhook: Parse, authenticate, log, and acknowledge webhook events.
Other paths: Return HTTP 404.
The bridge does not delete unrelated bot data or modify other webhooks. Once a webhook is registered, Zalo's getUpdates polling mode is unavailable until deleteWebhook is called.
高级特性
Dependency-free Node.js implementation using the built-in HTTP and filesystem modules.
Localhost-only binding to reduce direct network exposure.
HTTPS termination through Caddy, Nginx, cloudflared, ngrok, or another compatible tunnel.
Secret-token header compatibility across
x-bot-api-secret-token
,
x-zalo-secret
,
x-secret-token
, and related headers.
Request body protection with a 2 MB maximum size.
Structured NDJSON logging suitable for tailing, parsing, and downstream observability tools.
Persistent last-event capture for rapid webhook debugging.
systemd integration with startup on boot, automatic restart, and restart delay controls.
Health and challenge endpoints for reverse-proxy checks and webhook verification.
Explicit webhook lifecycle controls through getWebhookInfo, setWebhook, and deleteWebhook.
Owner-only execution guidance and secret-handling safeguards.
Clear operational limits for Zalo Bot plans, group membership, webhook-versus-polling behavior, and message history.
Composable with other Openclaw Skills, including a Zalo sending integration, to create a complete inbound and outbound bot workflow.
name: "ent-zalo-bridge" description: "Dựng bridge nhận tin Zalo Bot vào OpenClaw: viết service webhook (node, port 8787), cấu hình token + secret, trỏ domain HTTPS, đăng ký setWebhook với Zalo. Kèm script gửi tin."
ent-zalo-bridge — Dựng bridge WEBHOOK Zalo Bot (nhận tin vào)
Skill này giúp agent dựng hạ tầng nhận tin từ Zalo Bot vào OpenClaw: viết service webhook (node), cấu hình token + secret, trỏ domain HTTPS của máy, rồi đăng ký webhook với Zalo.
🔒 Phạm vi: tạo service node nghe webhook + đăng ký webhook với Zalo + cấu hình env. KHÔNG đụng dữ liệu khác, KHÔNG mở port công khai ra ngoài (chỉ 127.0.0.1 + domain trỏ vào), KHÔNG xoá webhook/dữ liệu của bot khác.
- KÍCH HOẠT — câu lệnh người dùng
Khi người dùng nói kiểu:
Dựng bridge Zalo cho anh. Token bot: . Secret: . Domain: https:///zalo/webhook
hoặc
Cài bridge nhận tin Zalo Bot
Agent PHẢI hiểu: dựng service webhook + đăng ký với Zalo.
Quy trình tự động
Trích ZALO_BOT_TOKEN, ZALO_SECRET (nếu có), DOMAIN (URL public HTTPS của máy) từ tin nhắn.
Kiểm tra token sống: getMe (mục 3.1). Lỗi → DỪNG.
Tạo service webhook (dùng code mục 4, hoặc copy zalo_webhook.js).
Cấu hình env: ZALO_BOT_TOKEN, ZALO_SECRET, PORT=8787.
Chạy service như systemd service (mục 6) để chạy 24/7.
Đăng ký webhook với Zalo: setWebhook (mục 3.3) trỏ tới https:///zalo/webhook.
Test: gửi 1 tin vào nhóm → kiểm tra webhook nhận được (log events.ndjson).
Báo kết quả ngắn: service chạy, webhook set, nhận tin OK.
⚠️ Thiếu token hoặc domain → hỏi lại đúng cái thiếu, KHÔNG đoán. ⚠️ Chỉ thực thi khi người gửi là owner.
- Kiến trúc bridge (hiểu đúng)
Zalo Bot Platform ──(HTTPS POST)──▶ https:///zalo/webhook
│
(reverse proxy / tunnel)
▼
node service (127.0.0.1:8787)
│
✔ xác thực Secret Token (header)
✔ log event → logs/events.ndjson
✔ trả 200 OK
Service node chỉ nghe 127.0.0.1:8787 (an toàn, không lộ trực tiếp).
Domain HTTPS trỏ về port đó (qua reverse proxy nginx/caddy, hoặc tunnel free như tino.page/ngrok/cloudflared).
Zalo phải gọi được URL public HTTPS → nếu không có, webhook không set được.
2. Yêu cầu hạ tầng
Thứ Bắt buộc Ghi chú
Node.js ≥ 18 ✅ Service webhook (dùng fetch built-in)
ZALO_BOT_TOKEN ✅ Dạng :
ZALO_SECRET Khuyến nghị Secret Token đặt trên dashboard Zalo Bot
Domain HTTPS public ✅ Trỏ về port 8787. Có thể: domain riêng + nginx/caddy, hoặc tunnel free
Port 8787 ✅ Mặc định; đổi qua env PORT
3. Lệnh API Zalo Bot
Base: https://bot-api.zaloplatforms.com/bot
3.1 Kiểm tra bot
curl -s "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/getMe"
3.2 Gửi tin (kiểm thử / nhắc việc)
curl -s -X POST "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/sendMessage" \
-H "Content-Type: application/json"
-d "{"chat_id":"$ZALO_CHAT_ID","text":"Nội dung"}"
3.3 Đăng ký webhook
curl -s -X POST "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/setWebhook" \
-H "Content-Type: application/json"
-d "{"url":"https:///zalo/webhook","secret_token":"$ZALO_SECRET"}"
ok: true → webhook đã đăng ký.
Sau khi set webhook: getUpdates KHÔNG dùng được nữa (lỗi 400). Bình thường.
3.4 Xem webhook hiện tại / gỡ webhook
# Xem
curl -s "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/getWebhookInfo"
# Gỡ (nếu muốn quay lại getUpdates)
curl -s -X POST "https://bot-api.zaloplatforms.com/bot$ZALO_BOT_TOKEN/deleteWebhook"
- Code service webhook (node, không dependency)
Đặt tại zalo-bridge/zalo_webhook.js:
#!/usr/bin/env node
'use strict';
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = parseInt(process.env.PORT || '8787', 10);
const TOKEN = process.env.ZALO_BOT_TOKEN || '';
const SECRET = process.env.ZALO_SECRET || '';
const API_BASE = 'https://bot-api.zaloplatforms.com/bot' + TOKEN;
const LOG_DIR = path.join(__dirname, 'logs');
const EVENT_LOG = path.join(LOG_DIR, 'events.ndjson');
fs.mkdirSync(LOG_DIR, { recursive: true });
function log(level, msg, extra) {
const line = JSON.stringify({ t: new Date().toISOString(), level, msg, ...(extra || {}) });
console.log(line);
fs.appendFileSync(EVENT_LOG, line + '\n');
}
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = []; let size = 0;
req.on('data', (c) => {
size += c.length;
if (size > 2 * 1024 * 1024) { reject(new Error('body too large')); req.destroy(); return; }
chunks.push(c);
});
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
req.on('error', reject);
});
}
function secretFromHeaders(h) {
return h['x-bot-api-secret-token'] || h['x-zalo-secret'] || h['x-secret-token'] ||
h['secret-token'] || h['x-zalo-signature'] || '';
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost');
const p = url.pathname;
if (req.method === 'GET' && ['/', '/health', '/zalo', '/zalo/health'].includes(p)) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, service: 'zalo-bridge', ts: new Date().toISOString() }));
return;
}
if (req.method === 'GET' && (p === '/zalo/webhook' || p === '/webhook')) {
const ch = url.searchParams.get('challenge') || url.searchParams.get('hub.challenge') || 'ok';
res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(ch); return;
}
if (req.method === 'POST' && (p === '/zalo/webhook' || p === '/webhook')) {
try {
const raw = await readBody(req);
const got = secretFromHeaders(req.headers);
let payload = null;
try { payload = JSON.parse(raw); } catch (_) {}
if (SECRET && got && got !== SECRET) {
log('warn', 'secret mismatch', {}); res.writeHead(401); res.end('unauthorized'); return;
}
log('info', 'webhook received', { secretPresent: !!got, payload });
if (payload) fs.writeFileSync(path.join(LOG_DIR, 'last_event.json'), JSON.stringify(payload, null, 2));
res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true }));
} catch (e) {
log('error', 'webhook error', { err: String(e) });
res.writeHead(500); res.end('error');
}
return;
}
res.writeHead(404); res.end('not found');
});
server.listen(PORT, '127.0.0.1', () => {
log('info', 'zalo-bridge listening', { port: PORT, hasToken: !!TOKEN, hasSecret: !!SECRET });
});
- Biến môi trường
Biến Mô tả
ZALO_BOT_TOKEN Token bot (:)
ZALO_SECRET Secret Token đặt trên dashboard Zalo (để xác thực webhook)
PORT Cổng nghe (mặc định 8787)
Lưu ý: token nằm trong path API Zalo (khác Meta). KHÔNG echo token ra chat/log.
- Chạy 24/7 bằng systemd (khuyến nghị)
Tạo file /etc/systemd/system/zalo-bridge.service:
[Unit]
Description=Zalo Bot webhook bridge
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/zalo-bridge
Environment=ZALO_BOT_TOKEN=
Environment=ZALO_SECRET=
Environment=PORT=8787
ExecStart=/usr/bin/node /opt/zalo-bridge/zalo_webhook.js
Restart=always
RestartSec=5
User=
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now zalo-bridge
sudo systemctl status zalo-bridge
- Trỏ domain HTTPS về port 8787 7A. ⭐ LẤY DOMAIN FREE (tino.page / tunnel) — cách đã dùng thật
💡 Domain KHÔNG đi kèm VPS. Mua VPS không tự có domain. Phải tự lấy 1 subdomain public HTTPS rồi trỏ về máy. Cách đã dùng thật ở hệ thống này: subdomain free của tino.page + Caddy.
Bước 1 — Lấy subdomain free:
tino.page cấp subdomain HTTPS free theo tên máy (vd host pttsnagb8 → pttsnagb8.tino.page). Có HTTPS sẵn, không cần mua/mua cert thủ công.
Tương đương: cloudflared (cloudflared tunnel --url http://127.0.0.1:8787), ngrok, localhost.run... — đều trỏ subdomain HTTPS về 127.0.0.1.
⚠️ Tunnel free thường gắn với máy/phiên — agent mới ở máy khác phải lấy tunnel riêng của nó.
Bước 2 — Caddy đẩy domain vào port 8787 (xem 7B).
Bước 3 — Kiểm tra + setWebhook:
curl -s https:///zalo/health
# {"ok":true,"service":"zalo-bridge",...}
Rồi setWebhook với https:///zalo/webhook (mục 3.3).
7B. ⭐ CADDY làm reverse proxy (cấu hình mẫu đã dùng thật)
Caddy giúp: nhận HTTPS từ domain → đẩy vào port nội bộ. File /opt/openclaw/Caddyfile mẫu:
{$DOMAIN:localhost} {
{$CADDY_TLS:tls internal}
# Zalo Bot webhook -> zalo-bridge (local)
handle /zalo/* {
reverse_proxy 127.0.0.1:8787
}
# Login page + auth API -> Management API on host
handle /login {
reverse_proxy 127.0.0.1:9998
}
handle /api/auth/* {
reverse_proxy 127.0.0.1:9998
}
reverse_proxy 127.0.0.1:18789 {
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
Chạy: caddy run --config /opt/openclaw/Caddyfile (hoặc để systemd quản lý). Biến DOMAIN truyền qua env; nếu chưa có domain riêng, Caddy tự cấp tls internal (cert nội bộ).
Lưu ý: khối handle /zalo/* là phần quan trọng — nó trỏ webhook Zalo vào 127.0.0.1:8787. Các khối khác (login, API, gateway) là của OpenClaw, giữ nguyên khi merge.
7C. Cách khác — Reverse proxy (nginx)
server {
listen 443 ssl;
server_name ;
ssl_certificate /etc/letsencrypt/live//fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live//privkey.pem;
location /zalo/webhook {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
}
}
7D. Cách khác — Tunnel free không cần proxy riêng
Khi chưa muốn dựng Caddy/nginx: dùng cloudflared, ngrok, hoặc tino.page — chúng tự trỏ subdomain HTTPS về 127.0.0.1:8787 (nhớ khớp đúng path /zalo/webhook).
- Test end-to-end
Gửi 1 tin vào nhóm Zalo (hoặc nhắn bot).
Kiểm tra log:
tail -5 logs/events.ndjson
cat logs/last_event.json
Thấy event webhook received + payload → bridge OK.
Không thấy → kiểm tra: domain trỏ đúng chưa, service chạy chưa, webhook đã set chưa (getWebhookInfo).
9. GIỚI HẠN Zalo Bot — nhớ (đừng hứa sai)
Giới hạn Chi tiết
KHÔNG tag/mention tên người @Tên chỉ là chữ thường → đẩy tên lên đầu tin
Trần BASIC 3 bot · 50 user/bot · 3.000 tin/tháng · 3 nhóm
Pro 129.000đ/tháng — trần cao hơn
Webhook XOR getUpdates Set webhook thì getUpdates không dùng được
Bot phải vào nhóm Add bot thủ công trước
Không moi tin cũ Bot không đọc tin trước khi vào nhóm
Rate limit Chưa công bố số tin/giây, tin/phút
10. Xử lý sự cố
Lỗi Nguyên nhân Cách xử lý
setWebhook lỗi URL Domain chưa HTTPS / không public Kiểm tra domain, cert, service chạy
secret mismatch trong log Secret trên dashboard khác env Đồng bộ ZALO_SECRET
Webhook 200 nhưng không log Sai path Đảm bảo Zalo trỏ /zalo/webhook
getUpdates lỗi 400 Đã set webhook Bình thường — dùng webhook
Service chết sau reboot Chưa systemctl enable enable --now
Không nhận tin Bot chưa vào nhóm / sai chat Add bot vào nhóm
11. Bảo mật — BẮT BUỘC
Chỉ nhận token/secret từ owner.
KHÔNG echo token/secret ra chat/log.
Service chỉ nghe 127.0.0.1 (không mở trực tiếp ra ngoài); public qua proxy/tunnel có HTTPS.
Đặt ZALO_SECRET để xác thực webhook; không nhận request không đúng secret.
KHÔNG chia sẻ token sang máy/agent khác.
12. Ghi chú kỹ thuật
Node ≥ 18 cần cho fetch built-in (hoặc thay bằng https module nếu Node cũ).
Log event dạng NDJSON tại logs/events.ndjson — dễ tail/parse.
Kết hợp với skill ent-zalo-connect (gửi tin) để thành bộ Zalo Bot đầy đủ: gửi + nhận.
Muốn bỏ webhook, quay về polling → deleteWebhook rồi dùng getUpdates.
水塔 AI 订阅价格对比