share_visited / share_reported 事件 + HMAC-SHA256 签名 + 审计日志
Webhooks
分享相关事件发生时,PageDrop 会主动推送到你的 URL。每次投递都用 webhook 的密钥做 HMAC-SHA256 签名,接收方可以验证真实性。
事件
| 事件 | 触发时机 |
|---|---|
share_visited | 分享被成功渲染一次(每次访问都触发,不只是首次) |
share_expiring_soon | (预留)—— 将来由定时任务触发,目前未启用 |
share_reported | 有人对这个分享提交了一份举报 |
订阅方可以挑一部分事件订阅;投递是 best-effort 模式(超时 5 秒),每次都会写入
WebhookDelivery 审计日志。
创建一个 webhook
只支持 session 鉴权(webhook 配置归控制台管,bot token 不能管理其它 token 或 webhook):
curl -X POST $BASE/api/webhooks \
-b cookies.txt \
-H "Content-Type: application/json" \
-d '{"url":"https://your.app/pagedrop-hook","events":["share_visited","share_reported"]}'
返回的 secret 只显示一次:
{
"webhook": { "id": "cm...", "url": "...", "events": ["share_visited","share_reported"], "active": true, "createdAt": "..." },
"secret": "whsec_AbC...DeF"
}
请把 secret 存到安全的地方——之后无法再次读取(要轮换就先删除再重建)。
请求形状
POST /your-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: PageDrop-Webhook/1.0
X-PageDrop-Event: share_visited
X-PageDrop-Signature: sha256=<hex>
{
"event": "share_visited",
"timestamp": "2026-05-23T08:30:12.345Z",
"data": {
"shareId": "cm...",
"token": "abcd1234",
"fileId": "f_...",
"fileName": "report.md",
"fileKind": "MARKDOWN",
"visitCount": 42,
"uniqueVisitCount": 18,
"ipPrefix": "203.0.113.0/24",
"referer": "https://twitter.com/i/web/..."
}
}
验证签名
import crypto from 'node:crypto';
function verify(req, secret) {
const sig = req.headers['x-pagedrop-signature'];
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(req.rawBody) // rawBody = 未解析的 UTF-8 请求体
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
X-PageDrop-Signature 缺失或不匹配的请求一律拒绝。
列表与审计
# 列出你的所有 webhook
curl -b cookies.txt $BASE/api/webhooks
# 单个 webhook 的最近 50 次投递历史
curl -b cookies.txt $BASE/api/webhooks/{webhookId}/deliveries
每条投递记录的字段:{ event, status, responseCode, attempts, deliveredAt }。
限制
- 每个用户最多 10 个活跃 webhook。
- 每次投递超时 5 秒;不自动重试(每个事件只触发一次)。
- 投递日志保留每个 webhook 最近 50 次(更早的会被静默清理)。