Facebook Messenger Bot 开发实战:从 Webhook 到智能助手

背景

Facebook Messenger 是全球最大的即时通讯平台之一,月活超过 10 亿。通过 Messenger Bot 可以构建自动化的客户服务、游戏助手、营销推广等应用。这篇文章记录我从零开始搭建 Messenger Bot 的过程,包括主页配置、Node.js Webhook 服务器搭建、消息处理逻辑实现,以及 Nginx HTTPS 部署的完整流程。

Messenger Bot 架构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────────────────────────────────────────────┐
│ Facebook Messenger Bot 架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户 开发者│
│ │ │ │
│ │ 发送消息 │ │
│ ▼ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Facebook │ ───────► │ Messenger │ ───────► │ Webhook │ │
│ │ Messenger │ │ Platform │ │ Server │ │
│ └──────────────┘ └──────────────┘ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │ Node.js │ │
│ │ Express │ │
│ └────────────┘ │
│ │
│ 关键组件: │
│ • Facebook 主页(Page)- 机器人的身份标识 │
│ • Messenger 应用 - 平台接入点 │
│ • Webhook 服务器 - 接收消息事件 │
│ • 消息处理逻辑 - 业务响应 │
│ │
└─────────────────────────────────────────────────────────────────────┘

前置条件与服务器要求

服务器环境要求

需求 说明 推荐方案
HTTPS 支持 Facebook 强制要求 Webhook 使用 HTTPS Nginx + SSL 证书
公网 IP 服务器必须能被 Facebook 访问 云服务器(ECS/VPS)
Node.js 运行环境 v8.x 或更高版本
域名 SSL 证书绑定 一级域名(推荐)

SSL 证书注意事项

重要提示:申请 SSL 证书绑定的域名建议使用一级域名(如 bot.example.com)。二级域名在某些 Nginx 配置场景下可能出现兼容性问题。阿里云等平台提供免费的 DV SSL 证书,有效期一年。

第一步:创建 Facebook 主页

Messenger Bot 必须关联一个 Facebook 主页,主页将作为机器人的”身份”与用户交互。

主页创建要求

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────┐
│ Facebook 主页创建规范 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 必填条件: │
│ ✓ 主页类别必须是"应用主页"或"公司、组织或机构" │
│ ✓ 主页名称需要包含应用名称 │
│ ✓ 主页不能与其他应用关联 │
│ │
│ 创建地址:https://www.facebook.com/pages/creation/ │
│ │
└─────────────────────────────────────────────────────────────┘

配置主页 Messenger 设置

创建主页后,需要进行以下配置:

  1. 进入主页的 设置 > Messenger 开放平台
  2. 通用设置 中找到 已订阅应用
  3. 点击 身份,选择 “Primary Receiver”(主要接收者)

关联小游戏(可选)

如果 Bot 用于游戏场景,需要将小游戏应用与主页关联:

  1. 进入小游戏应用的 详情 > 应用主页板块
  2. 选择刚创建的主页
  3. 注意:如果小游戏未与正确类型的主页关联,智能助手将无法收到 messaging_game_plays 事件

第二步:搭建 Node.js Webhook 服务器

项目初始化

创建项目目录并初始化:

1
2
3
mkdir messenger-bot-server
cd messenger-bot-server
npm init -y

package.json 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name": "messenger-bots",
"version": "1.0.0",
"description": "An webhook for Facebook Messenger.",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"body-parser": "^1.15.0",
"express": "^4.13.4",
"request": "^2.72.0"
},
"engines": {
"node": ">=8.0.0"
}
}

安装依赖:

1
npm install

核心服务代码:app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
'use strict';

const request = require('request');
const express = require('express');
const body_parser = require('body-parser');
const app = express().use(body_parser.json());

// ============ 配置常量 ============
const VERIFY_TOKEN = 'your_verify_token_here'; // Webhook 验证口令
const PAGE_ACCESS_TOKEN = 'your_page_access_token'; // 主页访问口令
const PORT = process.env.PORT || 1337;

// 启动服务器
app.listen(PORT, () => {
console.log(`Webhook server is listening on port ${PORT}`);
});

// ============ GET 请求:Webhook 验证 ============
/**
* Facebook 在配置 Webhook 时会发送 GET 请求进行验证
* 必须正确响应 challenge 参数才能完成验证
*/
app.get('/webhook', (req, res) => {
let mode = req.query['hub.mode'];
let token = req.query['hub.verify_token'];
let challenge = req.query['hub.challenge'];

if (mode && token) {
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
console.log('WEBHOOK_VERIFIED');
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
}
});

// ============ POST 请求:接收消息事件 ============
/**
* 所有 Messenger 事件(消息、游戏、回执等)都通过 POST 请求推送
*/
app.post('/webhook', (req, res) => {
let body = req.body;

if (body.object === 'page') {
body.entry.forEach(function(entry) {
let webhook_event = entry.messaging[0];
console.log('Received event:', webhook_event);

// 获取发送者 PSID(Page-Scoped ID)
let sender_psid = webhook_event.sender.id;
console.log('Sender PSID:', sender_psid);

// 根据事件类型分发处理
if (webhook_event.message) {
// 普通文本消息
handleMessage(sender_psid, webhook_event.message);
} else if (webhook_event.postback) {
// 按钮点击事件
handlePostback(sender_psid, webhook_event.postback);
} else if (webhook_event.game_play) {
// 小游戏游玩事件
handleGamePlay(sender_psid, webhook_event.game_play);
}
});

res.status(200).send('EVENT_RECEIVED');
} else {
res.sendStatus(404);
}
});

消息处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* 处理普通文本消息
* @param {string} sender_psid - 发送者 ID
* @param {object} received_message - 消息对象
*/
function handleMessage(sender_psid, received_message) {
let response;

if (received_message.text) {
// 根据关键词回复不同内容
const text = received_message.text.toLowerCase();

if (text.includes('help') || text.includes('帮助')) {
response = {
"text": "可用命令:\n• 游戏 - 开始游戏\n• 帮助 - 查看帮助\n• 状态 - 查看账户状态"
};
} else if (text.includes('游戏') || text.includes('game')) {
response = {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [{
"title": "我的小游戏",
"subtitle": "点击开始游玩",
"buttons": [{
"type": "game_play",
"title": "开始游戏"
}]
}]
}
}
};
} else {
// 默认回复
response = {
"text": `收到消息:"${received_message.text}"`
};
}
}

callSendAPI(sender_psid, response);
}

/**
* 处理按钮点击(Postback)事件
*/
function handlePostback(sender_psid, received_postback) {
let response;
let payload = received_postback.payload;

if (payload === 'GET_STARTED') {
response = { "text": "欢迎使用!发送"帮助"查看可用命令。" };
} else {
response = { "text": "收到按钮点击事件" };
}

callSendAPI(sender_psid, response);
}

/**
* 处理小游戏游玩事件
* 当用户通过 Messenger 游玩 Instant Game 时触发
*/
function handleGamePlay(sender_psid, game_play) {
let playerId = game_play.player_id; // Instant Games 玩家 ID
let contextId = game_play.context_id; // 游戏会话上下文 ID

console.log(`Player ${playerId} started playing in context ${contextId}`);

// 发送欢迎消息
let response = {
"text": `欢迎回来!你的玩家 ID 是 ${playerId}`
};

callSendAPI(sender_psid, response);
}

发送消息 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 调用 Facebook Send API 发送消息
* @param {string} sender_psid - 接收者 PSID
* @param {object} response - 消息内容
*/
function callSendAPI(sender_psid, response) {
let request_body = {
"recipient": {
"id": sender_psid
},
"message": response
};

console.log('Sending message:', JSON.stringify(request_body));

request({
"uri": "https://graph.facebook.com/v2.6/me/messages",
"qs": { "access_token": PAGE_ACCESS_TOKEN },
"method": "POST",
"json": request_body
}, (err, res, body) => {
if (!err) {
console.log('Message sent successfully!');
} else {
console.error('Unable to send message:', err);
}
});
}

第三步:Nginx HTTPS 配置

SSL 证书部署

将申请的 SSL 证书文件放置到服务器目录:

1
2
3
mkdir -p /data/ssl
cp your_certificate.pem /data/ssl/
cp your_private.key /data/ssl/

Nginx 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
server {
listen 443 ssl;
server_name bot.yourdomain.com;

# SSL 证书配置
ssl_certificate /data/ssl/your_certificate.pem;
ssl_certificate_key /data/ssl/your_private.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

# 加密套件配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

# 隐藏服务器版本号
server_tokens off;

# 全站 HTTPS 时启用 HSTS
add_header Strict-Transport-Security "max-age=31536000" always;

# FastCGI HTTPS 参数
fastcgi_param HTTPS on;
fastcgi_param HTTP_SCHEME https;

# 日志配置
access_log /var/log/nginx/messenger-access.log;
error_log /var/log/nginx/messenger-error.log;

# 反向代理到 Node.js 应用
location / {
proxy_pass http://localhost:1337;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect http:// $scheme://;

# WebSocket 支持(如需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name bot.yourdomain.com;
return 301 https://$server_name$request_uri;
}

使用 PM2 管理 Node.js 进程

1
2
3
4
5
6
7
8
9
10
11
12
13
# 全局安装 PM2
npm install -g pm2

# 启动应用
pm2 start app.js --name "messenger-bot"

# 设置开机自启
pm2 startup
pm2 save

# 查看运行状态
pm2 status
pm2 logs messenger-bot

第四步:配置 Messenger 平台

添加 Messenger 产品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────┐
│ Messenger 平台配置流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 进入 Facebook 开发者控制台 │
│ https://developers.facebook.com/apps/ │
│ │
│ 2. 选择你的应用 │
│ │
│ 3. 在左侧"产品"面板点击"+ 添加产品" │
│ │
│ 4. 找到 Messenger 并点击"设置" │
│ │
│ 5. 在 Messenger 设置页面配置以下项: │
│ │
└─────────────────────────────────────────────────────────────┘

配置 Webhook

在 Messenger 设置的 Webhooks 版块:

配置项 填写内容 说明
回调网址 https://bot.yourdomain.com/webhook 你的 Webhook 地址
验证口令 与 VERIFY_TOKEN 一致 用于验证请求合法性
订阅字段 messages, messaging_postbacks, messaging_game_plays 需要接收的事件类型

点击 “验证并保存”,Facebook 会发送 GET 请求验证你的服务器。

订阅主页并获取访问口令

  1. 生成口令 版块,选择之前创建的主页
  2. 复制 主页访问口令,更新到 app.jsPAGE_ACCESS_TOKEN 变量
  3. Webhooks 版块,选择同一主页并点击 订阅

Webhook 事件类型说明

事件类型 触发场景 用途
messages 用户发送文本/附件消息 处理用户输入
messaging_postbacks 用户点击按钮 处理菜单交互
messaging_game_plays 用户开始游玩 Instant Game 游戏场景联动
messaging_deliveries 消息送达确认 投递状态追踪
messaging_reads 消息已读确认 阅读状态追踪

进阶功能实现

发送结构化消息(卡片模板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function sendGenericTemplate(sender_psid) {
let response = {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [{
"title": "游戏礼包",
"image_url": "https://example.com/gift.png",
"subtitle": "每日登录领取超值礼包",
"buttons": [
{
"type": "postback",
"title": "领取礼包",
"payload": "CLAIM_GIFT"
},
{
"type": "web_url",
"url": "https://example.com/shop",
"title": "前往商店"
}
]
}]
}
}
};

callSendAPI(sender_psid, response);
}

设置欢迎界面(Get Started)

通过 Messenger Profile API 设置欢迎按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function setGetStartedButton() {
request({
"uri": "https://graph.facebook.com/v2.6/me/messenger_profile",
"qs": { "access_token": PAGE_ACCESS_TOKEN },
"method": "POST",
"json": {
"get_started": {
"payload": "GET_STARTED"
},
"greeting": [
{
"locale": "default",
"text": "你好!我是游戏助手,发送"帮助"开始探索。"
}
]
}
}, (err, res, body) => {
if (!err) {
console.log('Get Started button set successfully');
}
});
}

调试与常见问题

本地调试技巧

1
2
3
4
5
6
7
8
9
# 使用 ngrok 暴露本地服务
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt update && sudo apt install ngrok

# 启动隧道
ngrok http 1337

# 使用 ngrok 提供的 HTTPS URL 配置 Webhook

常见问题排查

问题 原因 解决方案
Webhook 验证失败 VERIFY_TOKEN 不匹配 检查 app.js 和 Facebook 控制台配置是否一致
消息发送失败 PAGE_ACCESS_TOKEN 无效 重新生成主页访问口令
无法接收游戏事件 主页未正确关联 确认主页类别和关联关系
SSL 错误 证书配置问题 检查 Nginx SSL 配置和证书路径

小结

搭建 Facebook Messenger Bot 的主要步骤:

  1. 创建主页:确保主页类别正确,与应用正确关联
  2. 搭建服务器:使用 Express.js 实现 Webhook 接收端点
  3. 部署 HTTPS:通过 Nginx 反向代理提供安全的 Webhook 地址
  4. 配置平台:在 Messenger 设置中完成 Webhook 验证和事件订阅
  5. 处理消息:根据业务需求实现消息解析和响应逻辑
  6. 测试验证:通过 Messenger 发送消息确认 Bot 正常工作

这套方案可以构建功能完善的 Messenger 智能助手,支持文本交互、卡片模板、游戏联动等多种场景。