用 Webhook 接收 Polymarket 交易提醒:接收、验签、去重
Webhook 把你关注的 Polymarket 交易者的每一笔成交变成发往你自己服务器的一个 HTTPS 请求 —— 用来在自己的群里发提醒、做看板,或者给不想一直挂着 WebSocket 的机器人用。下面讲怎样正确接收。
什么时候 Webhook 比 WebSocket 合适
WebSocket 需要一个一直连着的进程。Webhook 只需要一个 HTTPS 端点:一个 serverless 函数、一个小网站,或者一个转发到 Telegram、Discord、Slack 的中转。PMWallets 的 Webhook 至少投递一次、会重试,而 WebSocket 是尽力投递,所以当一笔都不能漏、多一点延迟无所谓时,Webhook 更合适。两者送来的成交对象完全相同。
注册并保管好密钥
在 PMWallets 推送页上为账户注册一个 HTTPS 端点,订阅交易者时选 webhook 通道。签名密钥只在注册时显示一次 —— 当场存好。端点必须是公网地址:localhost 和内网地址会被拒绝,也不跟随重定向。
对原始请求体验签
每个请求都带 x-pmw-signature:用你的密钥对原始请求体做 HMAC-SHA256,十六进制编码。要在解析之前对原始字节验签;解析再重新序列化 JSON 会改变字节,签名就全都对不上了。然后把这笔成交可靠地存下来 —— 写进数据库或持久化队列 —— 存好之后才返回 2xx:2xx 告诉 PMWallets 这次投递已完成,先回了 2xx 又丢掉的成交不会再发。存储失败就返回错误,它会被重试。
import { createHmac, timingSafeEqual } from "node:crypto"; app.post("/hook", express.raw({ type: "application/json" }), async (req, res) => { const sent = Buffer.from(req.header("x-pmw-signature") ?? "", "hex"); const mine = createHmac("sha256", process.env.PMW_SECRET).update(req.body).digest(); // compare the RAW body, before any JSON parsing: re-serialising changes the bytes if (sent.length !== mine.length || !timingSafeEqual(sent, mine)) return res.sendStatus(401); // store it durably BEFORE answering: a 2xx means "done" and we stop retrying, // so a fill acknowledged and then lost is gone for good. Anything else is retried. const fill = JSON.parse(req.body.toString()); try { await saveOnce(fill.eventId, fill); } catch { return res.sendStatus(503); } res.sendStatus(200); });
要预期会有重复
投递是至少一次。PMWallets 等待 5 秒的 2xx,最多尝试 3 次;发送和记录之间如果崩溃,也可能重放一次投递。把处理逻辑做成幂等:按 eventId 存储,已经存过的就忽略。
端点坏掉时
一个端点连续失败超过 24 小时、且连续失败至少 20 次,就会被停用,并发邮件通知你。只走 Webhook 的订阅随之暂停、不计费,直到你重新启用。停用期间错过的成交,可以从补发接口或交易历史导出取回。
常见问题
能在某个 Polymarket 钱包每次交易时收到通知吗?
能。在 PMWallets 上用 webhook 通道订阅这个交易者,每笔成交都会 POST 到你的端点,通常在出块后一两秒内。每个交易者每小时 $0.01,或包含在套餐里。
怎样验证 PMWallets 的 Webhook?
用你的签名密钥对原始请求体计算 HMAC-SHA256,与 x-pmw-signature 头里的十六进制值做常量时间比较。
为什么同一笔 Polymarket 成交我收到了两次?
Webhook 至少投递一次,重试或重放会让一笔成交重复。按 eventId 去重,它在每次重试中都不变。