发布通知
发布后主动通知你的地址,签名验证与重试规则
不想轮询图片包拉取接口,可以填一个接收地址:店主在网页上点「发布」、真的产生了新版本之后,拍图猫往这个地址 POST 一条消息。消息只带这一版的基本信息,不带图片和文字 —— 收到后用带 documents:read 权限的密钥去 GET /api/v1/image-pack-publications/<id> 拉内容。
设置在「开发者中心 → 发布通知」:填地址(只收 https://)、打开开关、抄走签名密钥。页面上的「发送测试通知」会立刻发一条 ping,用来确认地址通、验签对。
什么时候发
| 情况 | 发不发 |
|---|---|
| 点「发布」,版本号加一 | 发 |
同一把 Idempotency-Key 重复提交,没有产生新版本 | 不发 |
| 发布被拒(草稿有未保存修改、缺图、空包) | 不发 |
| 开关关着,或地址为空 | 不发 |
请求
POST <你的地址>
Content-Type: application/json
User-Agent: paitumao-webhook/1
X-Paitumao-Event: image_pack.published
X-Paitumao-Delivery: 7f5b0c9e-…
X-Paitumao-Timestamp: 1757600000
X-Paitumao-Signature: v1=3f1a…{
"event": "image_pack.published",
"publication": {
"id": 42,
"pack_id": "pk_xxxxxxxx",
"version": 3,
"style_code": "QS-2601",
"published_at": 1757600000000
}
}| 请求头 | 说明 |
|---|---|
X-Paitumao-Event | image_pack.published,或测试按钮发的 ping |
X-Paitumao-Delivery | 这一次投递的 id。重试时不变,按它去重 |
X-Paitumao-Timestamp | 发送时刻,Unix 秒。重试时刷新 |
X-Paitumao-Signature | v1= + 十六进制 HMAC-SHA256,见下 |
ping 的请求体只有 {"event":"ping"},没有 publication。
字段含义和拉取接口一致:id 是拉内容用的记录 id,pack_id 判同一件商品,style_code 只是展示。
验签
签名密钥在「开发者中心 → 发布通知」页面上,whsec_ 开头。算法:
- 取请求头
X-Paitumao-Timestamp的值ts和原始请求体body(不要先 parse 再序列化,字节要一模一样) - 用密钥算
HMAC-SHA256(secret, ts + "." + body),十六进制 - 和
X-Paitumao-Signature里v1=后面的值做常数时间比较 ts和当前时间相差超过 5 分钟就拒绝 —— 防重放
Node.js 参考实现:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, headers, rawBody) {
const ts = headers['x-paitumao-timestamp'];
const sig = headers['x-paitumao-signature'] ?? '';
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = Buffer.from(
createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex'),
'hex'
);
for (const part of sig.split(',')) {
const [v, hex] = part.trim().split('=');
if (v !== 'v1' || !hex) continue;
const given = Buffer.from(hex, 'hex');
if (given.length === expected.length && timingSafeEqual(given, expected)) {
return true;
}
}
return false;
}验不过就回 401 并丢弃。密钥泄露了就在页面上「重新生成」:旧密钥立即作废,之后的通知全用新密钥签。
超时与重试
- 单次请求 8 秒超时。你的接收端只需要「验签、记下 id、回
2xx」,拉内容放到之后做,不要在响应之前去拉。 - 非
2xx、连不上、超时都算失败,之后再试两次(间隔 2 秒、10 秒),三次都失败就放弃。 - 放弃不补发。你的系统本来就要按增量拉取的契约兜底:通知只是让你早一点拉,不是唯一来源。
- 重试的
X-Paitumao-Delivery不变,X-Paitumao-Timestamp和签名重新算。同一条通知可能收到两次,按 delivery id 或(pack_id, version)去重。 - 通知在发布之后的后台发,发不出去不影响发布本身;结果显示在设置页「上次投递」。
地址要求
- 只收
https://,不跟随重定向(3xx算失败)。 - 不收
localhost、内网主机名、IP 字面量 —— 拍图猫的服务器够不到它们。本机调试用一条公网可达的隧道(如cloudflared tunnel --url)。 - 地址里不能带用户名密码。
拍图猫文档