许多客户已经在使用WhatsApp了。对于支持请求、订单更新、预订提醒以及潜在客户的筛选工作,使用WhatsApp渠道往往比使用电子邮件效果要好得多。
但是,官方的WhatsApp Business Cloud API在部署过程中可能会比较耗时,主动发送的消息也受到模板限制,而且费用是按每次对话来计算的——当使用规模较大时,这些费用会迅速累积。
还有另一种解决方案:你可以在一台小型服务器上搭建自己的WhatsApp HTTP网关,将其与工作流引擎连接起来,这样所有往来消息都能被保存在你自己控制的基础设施中。这样一来,既没有每月的通话费用,也不需要为常规回复申请模板审批,更不会有第三方中间商掌握你的客户数据。
在本教程中,你将会按照这个方法来搭建自己的系统。最终,你将得到一个这样的WhatsApp机器人:
-
通过Webhook接收所有传入的消息
-
利用n8n工作流引擎来处理这些消息
-
根据关键词、人工智能技术或你选择的任何API自动回复用户
-
整个系统完全运行在你自己的服务器上,且仅使用两个开源工具
你会使用WAHA(WhatsApp HTTP API)作为网关,n8n作为工作流引擎。这两个工具都可以在Docker环境中运行,免费供个人自行托管使用。它们结合起来,能够满足从简单的自动回复到完整的CRM系统集成等各种需求。
目录
你将学到什么
-
WAHA的内部工作原理,以及何时应该使用它而不是官方的Cloud API。
-
如何使用Docker Compose同时运行WAHA和n8n。
-
如何扫描二维码并将WhatsApp账户绑定到你的网关上。
-
如何将WAHA的webhook连接到n8n工作流中。
-
如何构建基于关键词的自动回复机器人。
-
如何通过单独的工作流发送主动确认信息。
-
如何为生产环境优化配置(使用HTTPS、API密钥、设置速率限制以及启用队列模式)。
先决条件
-
一台Linux服务器(任何VPS都可以——对于小型机器人来说,2GB的RAM就足够了)。
-
已安装Docker和Docker Compose。
-
一个公共域名,其DNS解析指向该服务器;或者用于本地测试的ngrok隧道。
-
一个专门用于该机器人的WhatsApp账户(具体细节见下文)。
-
对JSON和HTTP请求有基本的了解。
你不需要具备n8n的使用经验。只要你能拖动元素并将其连接起来,就能构建相应的工作流程。
关于使用哪个WhatsApp账户的说明
WAHA是通过在无头Chromium进程中运行真实的WhatsApp Web会话来工作的。它会以真实账户的身份进行登录——就像你在浏览器中访问web.whatsapp.com一样。不过,Meta并不官方推荐将这种方式用于大规模的商业用途,因为如果某个号码发送的消息量过大,可能会导致账户被封禁。
因此,请为机器人专门准备一个号码,不要使用你的个人WhatsApp账号。可以购买第二张SIM卡、eSIM卡,或者支持WhatsApp激活的VoIP号码。只要保持发出的消息量在合理范围内,对于大多数小型企业来说就完全没有问题。
如果你计划每天发送数千条营销信息,那么应该使用官方的WhatsApp Business Cloud API——它正是为此而设计的。本教程主要针对那些需要实时控制功能,但又不需要企业级定价的场景,例如客服机器人、订单更新通知、预订确认等对话型流程。
WAHA与官方WhatsApp Business Cloud API的对比
在编写任何代码之前,了解何时应该使用哪种方案会很有帮助。
| 方面 | WAHA(自托管版) | WhatsApp Cloud API(Meta提供) |
|---|---|---|
| 设置流程 | 扫描二维码即可使用——几分钟内就能完成配置 | 需要企业验证和应用审核——耗时数天至数周 |
| 成本 | 仅需支付服务器费用 | 按每次对话收费 |
| 模板审批 | 不需要 | 对于24小时之外的主动发送消息,需要经过审核 |
| 会话模型 | 每个Core容器对应一个WhatsApp Web会话 | 使用原生API,无需Web会话 |
| 风险 | 如果发送大量未经请求的消息,账户可能会被封禁 | 存在速率限制,但正常使用不会导致账户被封禁 |
| 供应商锁定情况 | 没有——完全开源 | 受Meta的API和定价政策约束 |
| 适用场景 | 客服机器人、小型团队工作流程、内部工具 | 大规模营销活动、受监管的行业,以及每月需要发送超过10万条消息的场景 |
严格来说,两者并没有绝对的优劣之分。如果你为一家小型企业运营支持团队,那么WAHA通常是一个务实的选择;但如果你是一家需要发送大量交易信息的银行,那么Cloud API才是更合适的选择。许多团队会同时使用这两种方案——用WAHA处理对话式支持请求,用Cloud API处理大量的交易信息。
第1部分:了解WAHA
WAHA是一个开源项目,它将WhatsApp Web的功能封装在一个简洁的REST API中。你可以通过`POST /api/sendText`接口,传入聊天ID和消息内容,然后WAHA会负责将这些信息发送出去。你还可以配置一个Webhook地址,每当有新消息到达时,WAHA就会向该地址发送请求。
在内部实现上,WAHA会启动一个Chromium实例,打开WhatsApp Web界面,并使用特定的引擎(`whatsapp-web.js`、`NOWEB`或`GOWS`)来自动化整个会话处理过程。你的代码无需了解这些复杂的底层机制,只需要使用HTTP API即可完成相应的操作。
这个项目提供了两种版本:
-
WAHA Core——免费版,采用MIT许可证授权,每个容器只能支持一个活跃会话,提供社区技术支持。
-
WAHA Plus——商业版,支持多会话处理,提供优先技术支持,并可使用高级接口功能。
对于大多数只需要开发一个简单机器人的开发者来说,WAHA Core版本就已经足够了。以后也可以随时升级到更高版本。
官方文档链接为:waha.devlike.pro。建议在另一个标签页中打开这个页面,因为在后续内容中我们会经常提到具体的接口地址。
第2部分:使用Docker运行WAHA
首先为该项目创建一个新的目录:
mkdir whatsapp-bot && cd whatsapp-bot
然后创建一个`docker-compose.yml`文件:
services:
waha:
image: devlikeapro/waha:latest
container_name: waha
restart: unless-stopped
ports:
- "3000:3000"
environment:
- WAHA_DASHBOARD_ENABLED=true
- WAHA_Dashboard_USERNAME=admin
- WAHA_Dashboard_PASSWORD=change-me-now
- WHATSAPP_API_KEY=super-secret-key-change-me
- WHATSAPP_DEFAULT_engine=WEBJS
volumes:
- ./waha-sessions:/app/.sessions
需要注意的一些事项:
-
通过`WAHA_Dashboard_USERNAME`和`WAHA_Dashboard_PASSWORD`可以保护位于`http://your-server:3000`的Web界面。在公开暴露这个端口之前,一定要先更改这些默认值。
-
`WHATSAPP_API_KEY`是所有发送给WAHA的HTTP请求中必须包含在`X-Api-Key`头部中的关键信息。请像对待数据库密码一样严格保管它。
-
设置`WHATSAPP_DEFAULT_engine=WEBJS`意味着会使用成熟的`whatsapp-web.js`引擎。虽然WAHA也支持`NOWEB`和`GOWS`引擎,但出于安全考虑,对于首次部署来说,使用`WEBJS`是最稳妥的选择。
-
通过挂载卷,可以在容器重启后保持会话状态不变。如果不使用这个功能,每次重新构建容器时都需要重新扫描QR码来恢复会话信息。
启动容器:
docker compose up -d
docker compose logs -f waha
大约20秒后,WAHA会完成启动过程。请访问http://your-server:3000,并使用控制面板提供的账号信息进行登录。
第3部分:启动WhatsApp会话
在WAHA中,每个WhatsApp账户都被视为一个“会话”。您在同一时间只能在WAHA Core上使用一个会话。
从控制面板中,点击开始新会话,然后为其命名default。此时WAHA会显示一个QR码。
在您的手机上:
-
打开WhatsApp应用程序。
-
点击三点菜单(安卓系统)或设置选项(iOS系统)。
-
选择“已连接设备”→“关联设备”。
-
将手机摄像头对准屏幕上的QR码。
几秒钟后,控制面板会显示“正在运行”的状态,说明您的会话已经建立成功。
您也可以通过API来启动会话(会话名称为default,该名称会包含在URL路径中):
curl -X POST http://your-server:3000/api/sessions/default/start \
-H "X-Api-Key: super-secret-key-change-me"
这个请求是幂等的——如果会话已经正在运行中,执行该命令也不会产生任何效果。
如果需要将QR码保存为PNG格式文件,可以执行以下命令:
curl http://your-server:3000/api/default/auth/qr \
-H "X-Api-Key: super-secret-key-change-me" \
-H "Accept: image/png" \
--output qr.png
扫描该QR码后,您就可以进入WhatsApp会话了。
为了测试会话是否正常工作,您可以给自己发送一条消息:
curl -X POST http://your-server:3000/api/sendText \
-H "X-Api-Key: super-secret-key-change-me" \
-H "Content-Type: application/json" \
-d '{
"session": "default",
"chatId": "15555550123@c.us",
"text": "Hello from WAHA!"
}'
请将15555550123替换为您自己的电话号码(格式为国家代码+号码,不能包含+符号、空格或连字符)。后缀@c.us表示这是一个个人聊天会话;群组聊天会使用@g.us作为标识。
如果消息成功发送到了您的手机上,那就说明这个通信通道是正常工作的。
第4部分:运行n8n服务
在docker-compose.yml文件中,在WAHA配置旁边添加n8n服务的配置:
services:
waha:
# ... 现有的配置项
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_protocol=https
- WEBHOOK_URL=https://n8n.example.com/
- GENERIC_TIMEZONE=UTC
volumes:
- ./n8n-data:/home/node/.n8n
请将n8n.example.com替换为您的实际域名。如果只是进行本地测试,可以将其设置为:
- N8N_HOST=localhost
- N8N_protocol=http
- WEBHOOK_URL=http://localhost:5678/
如果你想在没有服务器的情况下在笔记本电脑上测试Webhook,可以在另一个终端中运行ngrok http 5678,然后将ngrok提供的HTTPS地址设置为WEBHOOK_URL。n8n会使用这个URL来指示外部服务应该将数据发送到哪里;如果设置错误,Webhook就会收到404错误响应。
启动开发环境:
docker compose up -d
访问http://your-server:5678。在第一次访问时,n8n会引导你创建一个账户(需要输入电子邮件地址和密码)。之后的每次访问都需要进行登录操作。为了在生产环境中增加安全性,建议将n8n部署在反向代理后面,并设置允许列表或额外的认证机制——这些我们稍后会进行配置。
第5部分:在n8n中创建Webhook触发器
点击“创建工作流”,你会看到一个空白的编辑界面。
添加一个Webhook节点并进行配置:
-
HTTP方法:POST
-
路径:
whatsapp(这个路径会出现在URL中) -
响应方式:立即响应
-
响应数据:以JSON格式发送第一条消息的数据
点击“监听测试事件”,n8n会显示两个URL:一个用于测试的URL,另一个用于正式使用的URL。请复制正式使用的URL,它的格式如下:
https://n8n.example.com/webhook/whatsapp
注意不要使用webhook-test这个URL——因为只有当编辑器处于打开状态时,这个URL才会被触发。你应该使用webhook这个URL。
第6部分:将WAHA与n8n连接起来
WAHA可以在发生任何WhatsApp事件时向Webhook发送数据。你需要指定这些数据应该被发送到哪个Webhook地址。
在WAHA的仪表板上,打开你的会话并设置Webhook URL。或者也可以通过API来设置:
curl -X PUT http://your-server:3000/api/sessions/default \
-H "X-Api-Key: super-secret-key-change-me" \
-H "Content-Type: application/json" \
-d '{
"config": {
"webhooks": [
{
"url": "https://n8n.example.com/webhook/whatsapp",
"events": ["message", "session.status"]
}
]
}
}'
message事件会在每次收到新的WhatsApp消息时被触发。session.status事件则会在会话连接、断开或重新连接时被触发——这对于在机器人出现故障时发出警报非常有用。
进行测试吧。用另一部手机给你的机器人的WhatsApp账号发送一条消息,然后回到n8n编辑器中。一两秒钟后,Webhook节点就会显示接收到的事件数据。
事件数据的大致格式如下:
{
"event": "message",
"session": "default",
"payload": {
"id": "false_15555550123@c.us_3EB0...",
"from": "15555550123@c.us",
"body": "Hello",
"timestamp": 1713801234,
"fromMe": false
}
}
所有你需要的信息都包含在payload字段中:消息的发送者(from)、发送的内容(body)以及消息发送的时间(timestamp)。
第7部分:构建第一个自动回复系统
仅仅能够接收用户输入的机器人是很无趣的。让我们让它也能做出回应吧。
你将构建一个简单的关键词路由系统:如果用户发送“hi”或“hello”,机器人会向他们打招呼;如果他们发送“price”,机器人就会发送价格信息;对于其他所有输入,系统都会采用默认的处理方式。
在Webhook节点之后,添加一个Switch节点。
配置Switch节点:
-
模式:表达式匹配
-
值:
{{ $json.payload.body.toLowerCase().trim() }} -
添加路由规则:
-
规则1:如果输入等于“hi”——输出0
-
规则2:如果输入等于“hello”——输出0
-
规则3:如果输入等于“price”——输出1
-
默认输出:2
-
在Switch节点之后,再添加三个HTTP请求节点,每个输出对应一个请求节点。
所有HTTP请求节点的配置都相同,唯一的区别在于发送的请求体内容:
-
方法:POST
-
URL:
http://waha:3000/api/sendText(在Docker网络中,可以通过服务名称访问WAHA;外部则需要使用完整的公共URL) -
请求头信息:启用
-
X-Api-Key:super-secret-key-change-me -
Content-Type:application/json
-
-
请求体内容:启用
-
内容类型:JSON
-
请求体内容:使用JSON格式发送
-
对于用于打招呼的节点,其JSON请求体内容如下:
{
"session": "default",
"chatId": "={{ $('Webhook').item.json.payload.from }}",
"text": "嗨!我是这个机器人。如果想了解价格信息,请发送‘price’;如需其他帮助,也可以发送其他内容。”
}
对于用于提供价格信息的节点,其JSON请求体内容如下:
{
"session": "default",
"chatId": "={{ $('Webhook').item.json.payload.from }}",
"text": "我们的套餐价格最低为49美元/月。如果想与人工客服沟通,请回复‘sales’。”
}
对于默认处理节点,其JSON请求体内容如下:
{
"session": "default",
"chatId": "={{ $('Webhook').item.json.payload.from }}",
"text": "我没有听清你的请求。请尝试发送‘hi’或‘price’。”
}
其中{{{ ... }}这种语法是一种n8n表达式——在运行时,它会从之前的节点中获取相应的值。
将Switch节点的各个输出端与对应的HTTP请求节点连接起来。保存工作流程设置,然后点击右上角的“激活”按钮。
用任何手机发送“hi”给你的机器人,它应该会在一秒钟内做出回应。
恭喜你——你已经成功创建了一个完全运行在你自己基础设施上的WhatsApp机器人。
第8部分:第二个示例——主动发送预订确认信息
自动回复功能确实很有用,但主动向用户发送信息时,其价值才会真正得到体现。下面是一个新的工作流程示例:每当数据库中新增一条记录时,这个系统就会自动发送预订确认信息。
在n8n中创建第二个工作流。可以使用以下触发器之一:
-
定时触发器 — 每分钟检查数据库一次,以获取新的记录
-
Webhook触发器 — 监听您的预订系统发送的通知
-
数据库触发器(适用于Postgres、MySQL、Supabase) — 实时响应数据表的插入操作
在这个示例中,使用定时触发器,设置每分钟检查一次数据库,然后使用Postgres的执行查询节点来读取尚未确认的预订信息:
SELECT id, customer_phone, service_name, booking_time
FROM bookings
WHERE confirmation_sent = false
LIMIT 20;
在Postgres节点之后,添加一个HTTP请求节点,该节点指向之前使用过的WAHA的sendText接口。请求内容如下:
{
"session": "default",
"chatId": "={{ $json.customer_phone }}@c.us",
"text": "您好!您在{{ \)json.booking_time }}预约的{{ \(json.service_name }}服务已经确认。如需重新安排时间,请回复‘change’。”
}
最后,再添加一个Postgres节点,将这些预订状态标记为“已发送”:
UPDATE bookings
SET confirmation_sent = true, confirmation_sent_at = NOW()
WHERE id = {{ $json.id.;
激活这个工作流后,n8n会每分钟检查一次尚未确认的预订信息,通过WhatsApp发送确认通知,并将这些预订状态标记为“已完成”。
这种模式具有通用性。您可以将SQL语句替换为调用Shopify来获取订单确认信息,或者使用Stripe来处理收款通知,再或者利用Calendly来发送预约提醒。不过,WhatsApp相关的部分保持不变——只有数据来源会发生变化。
第9部分:投入生产环境
上述设置虽然可以正常使用,但尚未达到生产环境的要求。在将真实客户的数据接入之前,还需要进行以下优化。
1. 将所有服务置于HTTPS环境中
切勿直接通过普通的HTTP协议暴露n8n或WAHA服务。应在前面添加反向代理服务器。Caddy是一个非常方便的选择,因为它能自动处理Let’s Encrypt证书的配置。
一个简单的Caddyfile配置文件如下:
n8n.example.com {
reverse_proxy n8n:5678
}
waha.example.com {
reverse_proxy waha:3000
}
可以在同一个Docker Compose环境中将Caddy作为单独的服务运行。TLS证书会自动生成并更新。
2. 更换API密钥
切勿将super-secret-key-change-me这样的临时密钥用于生产环境。请生成一个真正的API密钥:
openssl rand -hex 32
将新生成的密钥保存到.env文件中,在docker-compose.yml文件中将其设置为${WHATSAPP_API_KEY},同时确保.env文件不会被包含在.gitignore列表中。
3>限制对外发送消息的频率
WhatsApp会封禁那些频繁且快速发送大量消息的账户。对于新注册的账户来说,每分钟发送消息的数量应控制在20条以下。如果需要批量回复客户,可以在每次发送消息之前添加一个n8n的等待节点,或者通过一个自定义函数节点来控制消息发送的间隔时间。
4. 使用队列模式扩展n8n系统
默认情况下,n8n会在单个进程中运行所有任务。对于处理量较小的场景来说,这种配置完全适用。但当需要提高处理效率时,可以切换到队列模式:
-
添加一个Redis容器。
-
运行一个
n8n主容器(用于提供Web界面和接收Webhook请求)。 -
运行一个或多个
n8n-worker容器,这些容器会从队列中获取任务并执行相应操作。
关于队列模式的详细信息,请参阅docs.n8n.io/hosting/scaling/queue-mode/。配置队列模式需要设置两个环境变量:EXECUTIONS_MODE=queue和QUEUE_BULL_REDIS_HOST=redis),这样就能将接收到的Webhook请求与工作流程的执行过程分离开来。Webhook会立即作出响应,而后台的worker进程则会继续处理队列中的任务。
5. 监控会话状态
WhatsApp Web会话状态可能会突然发生变化——比如手机断开连接、WhatsApp更新了安全令牌,或者你的服务器重启了。因此,必须及时发现这些变化。
在WAHA系统中,订阅session.status这个Webhook事件。当会话状态变为FAILED或STOPPED时,立即将相关信息转发给n8n工作流程,让该流程在Slack上发布公告、发送电子邮件或通知你。越早发现问题,就越快能够恢复正常运行。
为了了解系统的整体运行状况,可以在WAHA系统中访问GET /api/sessions/default这个接口。如果系统显示“WORKING”,那就说明一切正常;否则就会触发警报。
6. 备份会话数据
waha-sessions目录中保存了用户的登录状态信息。如果这些数据丢失,用户就需要重新扫描QR码进行登录——而有时候可能找不到那部手机了。因此,建议每晚自动备份这些数据。使用tar和rclone>工具将数据备份到支持S3协议的存储系统中,这样就足够了。
7>添加实时客服转接功能
并不是所有的对话都应该由机器人来处理。当用户输入“human”这个词,或者当你的意图识别系统无法给出准确的回复时,就应该将对话转交给真实的客服人员。
Chatwoot是一个非常优秀的开源工具:它拥有专用的WhatsApp通道、客服人员的收件箱功能、团队协作机制以及对话记录存储功能。实现实时转接的方法是,在n8n系统中创建一个分支,让这个分支停止处理机器人的回复,而是将消息直接转发给Chatwoot的API。
常见问题与解决方法
在首次将系统部署到生产环境中时,几乎每个人都会遇到一些常见问题。
Webhook请求超时问题
WAHA会给你的Webhook请求几秒钟的响应时间。但如果n8n工作流程运行速度较慢(比如需要调用大型语言模型或访问远程API),Webhook请求可能会超时,从而导致WAHA重新尝试发送请求,进而可能产生重复的回复。
解决方法:让Webhook立即返回200状态码,从而跳过那些耗时的操作。在n8n系统中,将Webhook节点的响应模式设置为“使用Webhook节点进行响应”,然后创建一个响应节点,让它首先返回200状态码且不携带任何数据,之后再让其他任务继续执行。
重复消息
在某些特殊情况下,WAHA会多次发送相同的消息。例如,当手机重新连接网络或会话重新建立时,就会出现这种情况。请将payload.id存储在某个地方——比如Redis、数据库,或者n8n的静态数据存储系统中——并删除那些已经处理过的ID。
消息到达顺序混乱
Webhook是异步执行的,n8n也可以并行处理多个请求。如果消息的发送顺序非常重要(比如在多步骤对话中),那么应该按照发送者的chatId来对消息进行排序,并依次处理每个发送者的消息。
手机重启后会话中断
这是WhatsApp Web的正常行为。WAHA会自动重新连接,但有时需要手动刷新关联设备列表。如果某个会话无法恢复,请停止WAHA容器,删除waha-sessions/目录下该会话对应的文件夹,然后重新启动容器并重新扫描QR码。
你的号码被封禁了
导致号码被封禁的最主要原因就是发送消息的频率过高:如果一个新号码每小时发送数百条消息,很快就会被系统标记为异常。建议先缓慢地使用这个号码——在第一周内,发送数量要控制在正常、符合人类使用习惯的范围之内。不要主动向陌生人发送消息,尽可能采用对方发起的消息来回应你。
聊天ID格式错误
WhatsApp的个人聊天使用这种格式,而群聊则使用这种格式。在输入号码时,请不要包含+符号或空格。如果发送消息时出现404错误,几乎可以肯定是聊天ID的格式有问题。
下一步该怎么做
你现在已经掌握了基础知识。这套由两个服务组成的架构几乎可以支持你想象到的任何类型的机器人程序——唯一的限制在于你在n8n工作流中能够实现哪些功能。
接下来可以尝试的一些步骤包括:
-
添加AI回复功能:在Webhook之后添加一个OpenAI或Anthropic节点,将用户的消息传递给这个节点,并附上简短的系统提示信息,然后再通过WAHA将回复发送回去。这样可以帮助控制对话的长度,避免过度消耗token。
-
集成CRM系统:在决定如何回复之前,先在HubSpot、Pipedrive或你的数据库中查找来电者的
chatId。根据客户的等级来区分不同的回复内容。 -
主动发送通知:发送预约提醒、物流更新信息、付款确认函,或者提示用户处理被弃置的购物车。确保通知的内容具有事务性且符合用户的预期,否则不必要的营销信息很可能会导致你的号码被封禁。
-
记录每一条对话:在Webhook之后添加一个Postgres或Supabase节点,将所有消息保存下来,以便后续进行分析或查看客户历史记录。未来的你(以及你的技术支持团队)一定会为此感到庆幸的。
-
添加媒体处理功能:WAHA提供了
sendImage、sendFile和sendVoice这些接口。你可以让机器人接受用户上传的照片来处理支持请求,或者直接在聊天界面中以PDF格式发送发票。
WhatsApp相关的功能模块并没有发生变化;所有重要的处理流程都发生在工作流程的上游阶段。
如果您想了解n8n与WAHA在大规模应用环境中的实际运行效果,或者需要为自己的业务开发类似的自动化系统,那么我推荐您联系Achiya Automation。我们是提供WhatsApp、n8n以及Chatwoot集成服务的供应商,更多信息请访问 achiya-automation.com。




