QQ 机器人 Webhook 方式简易部署教程
官方最近对使用 AIGC 接口的机器人进行了封禁,且以往的 WebSocket 服务将陆续不再支持,故本文旨在以最为基础的办法,不使用外部框架,利用 Webhook 方式接入机器人。
准备工作
前往 QQ 开放平台 注册机器人账号。
沙箱配置
在**'沙箱配置'**中配置用于机器人测试的群聊、私聊账号以及频道信息。

开发管理
在**'开发管理'中可以看到机器人的 ID、密钥**等信息,很重要,后续需要使用。IP 白名单中需要放行你的服务器公网 IP。

回调配置
在**'回调配置'**中需要配置 Webhook 服务使用的回调地址,需要准备一个已备案的域名,并且域名需要解析到你的公网 IP 上。同时,域名需要部署 SSL 证书(能用 https 访问)。
建议配置如下后缀,后续配置 Webhook 时需要使用:
你的域名/qqbot-webhook/callback
填写好此时会提示'校验失败',不用着急,因为 Webhook 服务还没有启用,过一会儿会回来重填。
添加事件建议全选,也可以自选需要的,选好后需要在右下角确认配置。

阿里云的免费 SSL 个人测试证书不含 CA,回调地址验证时会失败,建议使用其他免费 SSL 证书。


代码部署
本文疏于解释的地方可参考 官方文档 和 官方源代码。
创建 python 环境
在自己的服务器上合适的位置(本文使用/opt/qqbot)创建用于管理机器人代码的文件夹,并从代码仓库导入文件。
git clone https://github.com/Space-ash/QQbot.git
在你的项目目录创建 python 虚拟环境。
安装依赖。
端口放行
阅读 官方 API 文档 中'事件订阅与通知'一节,回调地址允许配置的端口号为:80、443、8080、8443,因此需要在服务器对应的端口配置回调代码。
此外需要在服务器安全组放行 6196 端口。
| 端口 | 描述 | 类型 |
|---|
| 6195 | 企业微信默认端口 | 可选 |
| 6199 | QQ 个人号 (aiocqhttp) 默认端口 | 可选 |
| 6196 | QQ 官方接口 (Webhook) 默认端口 | 需要 |
本例使用 Apache,如果你需要使用 Nginx 或 Caddy 等,可以自行查阅相关资料。
找到解析到服务器的域名对应的 Apache 配置文件,文件名通常为"你的域名.conf"。
<VirtualHost *:80>
# ... 此处是默认有的一大段代码,不用管 ...
# 只用插入这一段,反代 Webhook 回调
ProxyPass "/qqbot-webhook/callback" "http://127.0.0.1:6196/qqbot-webhook/callback"
ProxyPassReverse "/qqbot-webhook/callback" "http://127.0.0.1:6196/qqbot-webhook/callback"
</VirtualHost>
<VirtualHost *:443>
# ... 此处是默认有的一大段代码,不用管 ...
# 只用插入这一段,反代回调,需要在相应的地方改为你的回调地址后缀
ProxyPass "/qqbot-webhook/callback" "http://127.0.0.1:6196/qqbot-webhook/callback"
ProxyPassReverse "/qqbot-webhook/callback" "http://127.0.0.1:6196/qqbot-webhook/callback"
# 如果以后用 WS,把升级头转过去
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule ^/qqbot-webhook/(.*) ws://127.0.0.1:6196/qqbot-webhook/$1 [P,L]
</VirtualHost>
重启 Apache 服务,加载配置文件。
service httpd restart
qqbot_webhook.py
使用 Webhook 服务的主要文件,打开后你需要填入'开发管理'中查看到的机器人 ID 和密钥。
APP_ID = "your_app_id_here"
BOT_SECRET = "your_bot_secret_here"
如果需要保存日志,则将此处的 LOG_DIR 改为你的地址。
LOG_DIR = "/opt/qqbot/logs"
os.makedirs(LOG_DIR, exist_ok=True)
def write_log(level: str, msg: str):
"""单文件临时日志,全部写到 qqbot_webhook.log"""
path = os.path.join(LOG_DIR, "qqbot_webhook.log")
line = f"{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} | {level} | {msg}\n"
with open(path, "a+", encoding="utf-8") as f:
f.write(line)
f.flush()
回调地址后缀"/qqbot-webhook/callback"则需要改为你希望填写在'回调配置'中的地址后缀。
@app.post("/qqbot-webhook/callback")
async def qqbot_callback(request: Request):
write_log("INFO", "=== 收到 Webhook 请求 ===")
raw = await request.body()
qqbot_webhook.service
Service 文件用于后台运行 Webhook 服务,需要将 WorkingDirectory 改为你的 service 地址,ExecStart 改为你的 uvicorn 地址。
[Unit]
Description=QQ Bot Webhook (FastAPI)
After=network.target
[Service]
User=www-data
WorkingDirectory=/opt/qqbot
ExecStart=/opt/qqbot/.venv/bin/uvicorn qqbot_webhook:app --host 127.0.0.1 --port 6196
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
启用 Webhook 服务
把你的项目路径下的 service 单元链接进系统搜索路径。
sudo systemctl link /opt/qqbot/qqbot_webhook.service
启用服务,修改后需重启服务。
sudo systemctl daemon-reload
sudo systemctl enable --now qqbot_webhook
sudo systemctl restart qqbot_webhook
sudo systemctl status qqbot_webhook
验证服务状态
curl -v -i http://127.0.0.1:6196/qqbot-webhook/callback \
-H 'Content-Type: application/json' \
-d '{"op":13,"d":{"plain_token":"Arq0D5UvOp","event_ts":"1741"}}'
如果终端返回 200 OK 以及一个含有"plain_token"和"signature"的 json,则说明服务启用成功了。
* Trying 127.0.0.1:6196...
* Connected to 127.0.0.1 (127.0.0.1) port 6196
> POST /qqbot-webhook/callback HTTP/1.1
> Host: 127.0.0.1:6196
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 76
< HTTP/1.1 200 OK
< date: Tue, 21 Oct 2025 13:37:25 GMT
< server: uvicorn
< content-length: 181
< content-type: application/json
< * Connection
{,}
若出现错误,可以在终端查看日志排查。
journalctl -u qqbot_webhook -n 100 -f
成功后回到官方的'回调配置'网页,在请求地址中填入你的回调地址,发现不再提示校验失败。
你的域名/qqbot-webhook/callback
保存配置后,在终端进入项目的 python 虚拟环境,并运行机器人。
source .venv/bin/activate
python3 ./yourbot/demo_yourbot.py
可以看到终端输出,则说明机器人启动成功了。
[INFO] (client.py:162)_bot_login [botpy] 登录机器人账号中...
[INFO] (robot.py:65)update_access_token [botpy] access_token expires_in 2964
[INFO] (client.py:181)_bot_init [botpy] 程序启动...
[INFO] (connection.py:60)multi_run [botpy] 最大并发连接数:1, 启动会话数:1
[INFO] (client.py:242)bot_connect [botpy] 会话启动中...
[INFO] (gateway.py:115)ws_connect [botpy] 启动中...
[INFO] (gateway.py:142)ws_identify [botpy] 鉴权中...
[INFO] (gateway.py:85)on_message [botpy] 机器人「说怪话 - 测试中」启动成功!
[INFO] (gateway.py:223)_send_heart [botpy] 心跳维持启动...
[INFO] (demo_chimera.:) 「说怪话 测试中」!
私聊机器人即可收到回复。

如果机器人没有回复,可以查看 logs/qqbot_webhook.log 中的信息,或者利用 journalctl -u qqbot_webhook -n 100 -f 查看终端的运行日志进行调试。
功能扩展示例
发送私聊消息 C2C_MESSAGE_CREATE
以下是私聊消息处理的核心代码。
import botpy
from botpy.http import BotHttp
from botpy.api import BotAPI
from botpy.message import C2CMessage
from botpy.types.gateway import MessagePayload
from yourbot.demo_yourbot import MyClient
def build_message_payload(event: dict) -> MessagePayload:
return {
"author": event.get("author", {}),
"channel_id": event.get("channel_id", ""),
"content": event.get("content", ""),
"guild_id": event.get("guild_id", ""),
"id": event.get("id", ""),
"member": event.get("member", {}),
"message_reference": event.get("message_reference", {}),
"mentions": event.get("mentions", []),
"attachments": event.get("attachments", []),
"seq": event.get("seq", 0),
"seq_in_channel": event.get("seq_in_channel", ),
: event.get(, ),
}
op == :
event = payload.get(, {})
event_type = payload.get(, )
event_type == :
write_log(, )
http = BotHttp(timeout=, app_id=APP_ID, secret=BOT_SECRET)
api = BotAPI(http)
event_id = event.get(, )
payload = build_message_payload(event)
message = C2CMessage(api, event_id, payload)
intents = botpy.Intents(public_messages=)
client = MyClient(intents=intents)
client.on_c2c_message_create(message)
关联代码片段:demo_yourbot.py
import botpy
from botpy import logging
from botpy.ext.cog_yaml import read
from botpy.message import C2CMessage
test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))
_log = logging.get_logger()
class MyClient(botpy.Client):
async def on_ready(self):
_log.info(f"robot「{self.robot.name}」on_ready!")
async def on_c2c_message_create(self, message: C2CMessage):
_log.info(f"收到消息:{message.content} 来自 {message.author.user_openid}")
await message._api.post_c2c_message(
openid=message.author.user_openid,
msg_type=0,
msg_id=message.id,
content=f"我收到了你的消息:{message.content}"
)
关联代码片段:message.py
class C2CMessage(BaseMessage):
__slots__ = ("author",)
def __init__(self, api: BotAPI, event_id, data: gateway.MessagePayload):
super().__init__(api, event_id, data)
self.author = self._User(data.get("author", {}))
def __repr__(self):
slots = self.__slots__ + super().__slots__
return str({items: str(getattr(self, items)) for items in slots if not items.startswith("_")})
class _User:
def __init__(self, data):
self.user_openid = data.get("user_openid", None)
def __repr__(self):
return str(self.__dict__)
async def reply(self, **kwargs):
return await self._api.post_c2c_message(openid=self.author.user_openid, msg_id=self.id, **kwargs)
关联代码片段:api.py
class BotAPI:
""" 机器人相关的 API 接口类
使用注意:
- 如果要直接使用 api,可以通过 client 的内部成员变量,通过`self.api.xx`来使用
- 设置超时时间:Client(timeout=5)
- API 当前返回的所有自定义类型数据为字典数据,通过 TypedDict 进行类型提示
"""
def __init__(self, http: BotHttp):
""" Args: http (BotHttp): 用于发送请求的 http 客户端。 """
self._http = http
关联代码片段:http.py
class BotHttp:
""" TODO 增加请求重试功能 @veehou TODO 增加并发请求的锁控制 @veehou """
def __init__(self, timeout: int, is_sandbox: bool = False, app_id: str = None, secret: str = None,):
self.timeout = timeout
self.is_sandbox = is_sandbox
self._token: Optional[Token] = None
if not app_id else Token(app_id=app_id, secret=secret)
self._session: Optional[aiohttp.ClientSession] = None
self._global_over: Optional[asyncio.Event] = None
self._headers: Optional[dict] = None
关联代码片段:gateway.py
class MessagePayload(TypedDict):
author: UserPayload
channel_id: str
content: str
guild_id: str
id: str
member: Member
message_reference: MessageRefPayload
mentions: List[UserPayload]
attachments: List[MessageAttachPayload]
seq: int
seq_in_channel: str
timestamp: str