询盘

把询盘通知接进飞书群:
从建机器人到收到第一条消息

建机器人、curl 试通、算签名、放进 Worker。一小时能配完,不需要额外服务。

文 / Mark 2026-11-06 询盘 / 飞书通知

表单提交之后,飞书群里出现一条消息。这篇只讲怎么配出来。

配完的效果是,有人填了询盘,你手机上响一下,点开就是内容。不用再一天刷五遍邮箱。

整个过程可以拆成五步:建机器人、选安全设置、用 curl 试通、算签名、放进 Worker。全部做完,熟练的话一小时以内。不需要额外服务,飞书自定义机器人本身不收费。

一、配完之后是什么样

先看结果,省得中途不知道自己在往哪走。表单提交,群里出现这样一条:

新询盘|德国|Anna Weber
产品:30W 一体机 x 200
留言:请报 FOB 宁波价,10 月底前要
2026-11-06 09:12

五行以内,手机上不折叠就能看全。点末行那个时间,跳到你自己的询盘记录页。

要靠的就是一个 HTTP 请求,往飞书给的那个地址 POST 一段 JSON。后面几步都是围着这一个请求转。

二、建一个自定义机器人

在目标的飞书群里点:群设置 → 群机器人 → 添加机器人 → 自定义机器人。给个名字,比如「网站询盘」,加进群。

完成后飞书会给你一个地址,长这样:

https://open.feishu.cn/open-apis/bot/v2/hook/8f3a1c2e-xxxx-xxxx-xxxx-xxxxxxxxxxxx

这个地址本身就是凭证。谁拿到它,谁就能往这个群里发消息。飞书官方文档里专门写了一句话:不要公布在 Gitlab、博客等可公开查阅的网站上,避免地址泄露后被恶意调用发垃圾消息。

所以这个地址只该放在两个地方:Worker 的环境变量,和你的密码管理器。不要写进代码仓库。

三、选一种安全设置

同一个页面往下拉,有三种安全设置,可以叠加,也可以只开一种。按你的情况选:

设置怎么起作用适合谁
自定义关键词消息文本里必须出现其中一个词,最多 10 个想五分钟就配完
IP 白名单只接受指定地址的请求,最多 10 个,支持 123.12.1.*有固定出口 IP 的服务器
签名校验时间戳加密钥算 HmacSHA256,对不上直接拒站放在 Worker、Vercel 这类平台上

站放在 Cloudflare Worker 上的,白名单这条路走不通:Worker 的出口 IP 不固定,你没有确定的值可以填。

实际能用的组合有两个。

只想快点跑通:开关键词,填一个每条消息都会带的词,比如 询盘。你的消息模板里出现这两个字就算过。

想稳一点:开签名校验,多写十行代码,第五步给。签名开了就不用关键词,两者同时开会互相卡。

关键词只对文本字段生效。 text、title 这类参数里出现才算,你设在链接的 href 里不算,图片里的文字也不算。

四、用 curl 先试通

写代码之前,先用一条命令确认地址是对的、群是对的。这一步能省掉后面一半的排查。

HOOK="https://open.feishu.cn/open-apis/bot/v2/hook/你的地址"

curl -s -X POST "$HOOK" \
  -H "Content-Type: application/json" \
  -d '{"msg_type":"text","content":{"text":"测试:新询盘|德国|Anna Weber"}}'

返回这样就算通了:

{"code":0,"msg":"success","data":{}}

code 是 0 才算成功。飞书有时候 HTTP 返回 200,code 却不是 0,要看 body,不要只看状态码。

如果这一步就失败,先对照第八节的返回码表,通常当场就能定位。地址错、关键词没匹配、机器人被移出群,都在这时候暴露,比写进代码之后再去翻日志省事。

五、算签名

这一段只跟开了签名校验的人有关。如果第三节你选的是关键词,可以直接跳到第六节。

开了签名的,每个请求多带两个字段:timestamp 和 sign。

“把 timestamp + "\n" + 密钥 作为签名字符串,使用 HmacSHA256 算法计算签名,再对结果做 Base64 编码。”

这是官方文档对这个算法的描述。它容易被读反:拼接出来的那段是 HMAC 的密钥,要签的数据是空字符串。把 ts 当数据去签,报的就是 19021。

飞书开放平台 · 自定义机器人使用指南
async function feishuSign(secret, ts) {
  const key = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(`${ts}\n${secret}`),
    { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
  );
  const buf = await crypto.subtle.sign("HMAC", key, new Uint8Array(0));
  return btoa(String.fromCharCode(...new Uint8Array(buf)));
}

new Uint8Array(0) 就是那个空字符串。

两个常见的错法,都报同一个错误码 19021:

  1. 对 ts 本身签名,而不是对空字符串签
  2. timestamp 用了毫秒。这个字段要的是秒,Math.floor(Date.now() / 1000)

签名有效期一小时(3600 秒),timestamp 要现算,不能写死。

想先用 curl 验证签名对不对,可以这样(HOOK 沿用上一步那个变量):

TS=$(date +%s)
SECRET="你的密钥"
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$(printf '%s\n%s' "$TS" "$SECRET")" -binary | base64)

curl -s -X POST "$HOOK" -H "Content-Type: application/json" \
  -d "{\"timestamp\":\"$TS\",\"sign\":\"$SIGN\",\"msg_type\":\"text\",\"content\":{\"text\":\"测试:带签名\"}}"

六、把推送放进 Worker

下面是 Cloudflare Worker 里能直接用的写法,配合表单那条 POST 一起跑。

export default {
  async fetch(request, env, ctx) {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    const form = await request.formData();
    const data = {
      name:  String(form.get("name")  || "").slice(0, 60),
      email: String(form.get("email") || "").slice(0, 120),
      msg:   String(form.get("message") || "").slice(0, 300),
    };

    // feishuSign 见第五步
    const ts = Math.floor(Date.now() / 1000);
    const body = {
      timestamp: String(ts),
      sign: await feishuSign(env.FEISHU_SECRET, ts),
      msg_type: "text",
      content: { text: [
        `新询盘|${data.name}`,
        data.email,
        data.msg,
      ].join("\n") },
    };

    // 推送交给后台,访客不用等
    ctx.waitUntil(fetch(env.FEISHU_HOOK, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    }).then(r => r.text()).then(t => console.log("feishu:", t)));

    return new Response("ok", { status: 200 });
  },
};

三个地方值得说明。

1. 为什么用 waitUntil

是为了让访客不用等。 推送放进后台跑,请求立刻返回,访客点完提交马上看到成功页。

写成 await fetch(...) 也能发出去,但飞书那边慢半秒,访客就多等半秒。

waitUntil 在响应发出后最多再跑 30 秒。飞书正常几百毫秒就回,够用。

2. 推送之前先存一份原文

发一封邮件给自己、写进 D1 或者 KV,随便哪种。这样即使推送失败,这条询盘还在。顺序别反:先存,后推。

3. 字段截断一下

slice 那几行是防止有人塞一万字进来,撑爆 20 KB 的请求体上限。

七、消息怎么写才好读

飞书的 text 消息支持换行,不支持 Markdown。冒号、竖线、方括号都是普通字符,排版只能靠换行和符号。

一条管用的询盘通知,通常有这几行:

  • 第一行给判断要不要现在看的信息:来源站点、客户名或公司名
  • 第二行给判断值不值得跟的信息:产品、数量
  • 第三行给原文:留言,截到 300 字
  • 最后一行给入口:后台链接或提交时间

不要把整个表单原样贴上去。字段一多,手机上要滑两屏,人就不看了。

关于 @ 人

飞书支持 <at user_id="ou_xxxx"></at>,能直接叫醒指定的人。用之前想清楚,一天十几条 @ 会让人把群免打扰。带附件的询盘 @ 一次,其余的不用。

查询 user_id 要调通讯录接口,嫌麻烦的话,用 <at user_id="all"></at> 也行,但那个是 @ 所有人,慎用。

八、报错码对照

出问题基本都在这一张表里。

code提示通常的原因
19021sign match fail or timestamp is not within one hour对 ts 签名而不是空字符串,或 timestamp 用了毫秒,或后台重置过密钥
19024Key Words Not Found关键词没出现在文本字段里,或者只写在链接、图片里
19022Ip Not Allowed开了白名单,但请求来源不在里面
9499Bad RequestJSON 格式错误,或者请求体超过 20 KB
11232频率超限单机器人 100 次/分钟、5 次/秒,整点高峰容易撞上

机器人被移出群、群被解散这类情况,返回值不固定,有时是 9499,有时什么都不返回。最快的判断办法是去群里看一眼机器人还在不在。

19021 里有一半是密钥问题。 后台点过「重置」,旧密钥当场失效,代码里那个再怎么算都签不过。改了密钥记得同步改环境变量。

九、怎么确认它还在工作

推送坏掉的时候是安静的。 函数不报错,飞书也不报错,就是没消息。

1. 每周手动测一次

配完之后给自己留一个固定动作:每周挑一天,提一次测试表单,看群里有没有出现。三十秒的事。

2. 按静默天数判断

如果站上询盘本来就不多,可以换个判断方式:按过去三个月的日均量定一个天数,超过这个天数零消息,就去翻一眼 Worker 日志。

一个每月二十条的站,连续五天没动静基本可以确认有问题。一个月两条的站,这个办法不适用,还是用每周手动测。

你按这个顺序往下查:先看表单有没有触发函数,再看飞书返回的 code,最后确认机器人还在群里。三层依次走,比乱猜快。

十、留三行记录

只有你自己知道怎么配的,那它就只能由你维护。 配完之后,在运维笔记里写三行:

  • webhook 地址和密钥存在哪儿
  • 询盘原文存在哪儿
  • 改了密钥或换了群,需要同步改哪一处

下次换人接手、或者半年后自己回来看,这三行能省掉重新摸一遍的时间。

常见问题

Q1飞书自定义机器人要付费吗?

不要。群机器人本身免费,只受频率限制:单个机器人 100 次/分钟、5 次/秒。需要付费的是企业内部应用的接口权限,自定义机器人不在这套体系里。

Q2关键词和签名,两个都开行不行?

能开,但没必要。关键词校验的是消息文本里有没有那个词,签名校验的是请求来源。两个同时开,只要消息里没带那个词,签名算对了照样被拒。选一个就够。

Q3静态站没有服务器,怎么发这个请求?

用一个 Cloudflare Worker 接住表单那条 POST,在 Worker 里发出去。不要在浏览器里直接发:webhook 地址会暴露在前端代码里,任何人都能拿去往你群里灌消息。

Q4机器人被移出群,函数还能发成功吗?

返回值不固定,有时是 9499,有时 body 是空的,函数本身不会报错。所以每周手动提交一次测试表单、看群里有没有出现,比盯日志靠谱。

资料来源

  1. 飞书开放平台 · 自定义机器人使用指南(webhook 地址格式、三种安全设置、签名算法与 1 小时有效期、100 次/分钟与 5 次/秒频率限制、20 KB 请求体上限、19021 / 19022 / 19024 / 9499 / 11232 等错误码、整点限流提醒、密钥重置后失效)
  2. 文中 ctx.waitUntil 的 30 秒上限、「先存原文再推送」、字段截断长度、消息模板的排序方式,是常见的工程做法,不是飞书文档的强制要求。
继续看

配一次,以后每天少刷几遍邮箱。

更多文章 邮件为什么进垃圾箱 · 网站日志该向谁要 · 如何让 ChatGPT 引用你的站 · 外贸站 CDN 验收清单 · 如何把 Clarity 数据接入 AI 助手 · 如何把 GA4 数据接入 AI 助手 · 销售产品链接验收 · 外贸站页面治理清单 · 产品图一张 4MB,先换图 · 选错「搭法」比选错服务商更坑 · 国内访问慢怎么解 · 外贸线上获客预算怎么花 · 2026 年了,用 AI 重构你的站 · 询盘图纸附件验收 · 外贸站该换个「地基」了 · 外贸站改版发布排期 · WordPress 迁移会丢 SEO 权重吗 · 网站结构化数据验收 · WordPress 网站太慢怎么办 · 建站服务商失联接管 · 外贸网站被黑了怎么办 · 网站缓存更新验收 · 独立站没询盘怎么办 · 外贸站修改需求写法 · 外贸建站源码交付 · 网站字体加载验收 · 外贸建站多少钱 · 外贸站内容更新排期 · WordPress 建站 vs 纯静态站 · 网站解析与邮箱安全 · WordPress 的那些坑 · 外贸站托管服务边界 · GEO 时代的静态站结构 · Core Web Vitals 是订单漏斗 · WAF 开了表单收不到 · 没后台的网站黑客打什么 · 迁静态站最坑的是 301 · site: 只搜到首页 · 多语言站的 hreflang · 外贸站海外打开慢的五个地方

免费

想加一条询盘提醒?
把表单和群发我,我按这套给你配上。

发邮件
  1. 01
    发网址,一句话说清你在担心什么企业微信或邮件都行,不用先填表、不用先注册。
  2. 02
    我按你给的地址实测一遍挑三个有询盘价值的页面,速度、抓取、表单各跑一次。
  3. 03
    当天给结论,能自己改的给清单该换的也直说,并告诉你换完能省下哪几笔。