Webhook
带签名的事件投递:可用事件、用 JavaScript 和 Python 验证 X-LinkProfit-Signature、重试机制与失败状态。
更新于 2026年8月13日
Webhook 以 JSON POST 请求的形式把事件推送到你的端点,无需轮询。可以在控制台中注册端点(设置 → Webhook),也可以通过 POST /v1/webhooks 注册;签名密钥只在创建时显示一次。
事件
| 事件 | 触发时机 | 适用范围 |
|---|---|---|
| link.created / link.updated / link.deleted | 短链接发生变更——通过控制台、API 或批量导入 | 工作区端点和合作伙伴端点 |
| domain.pending_ssl | DNS 已验证,证书正在签发 | 工作区和合作伙伴 |
| domain.activated | 该域名开始承载流量 | 工作区和合作伙伴 |
| domain.error | 开通失败,详情见载荷内容 | 工作区和合作伙伴 |
| click.threshold | 达到每月可统计点击量上限的 80% / 100% | 工作区和合作伙伴 |
| conversion.created | 记录了一笔转化:归因到某次点击的订单、注册或自定义转化目标 | 工作区和合作伙伴 |
| workspace.subscribed | 客户支付了订阅的第一张账单 | 仅合作伙伴 |
| workspace.past_due | 客户付款失败 | 仅合作伙伴 |
| workspace.suspended | 客户被暂停(催缴或人工操作) | 仅合作伙伴 |
| payment.succeeded | 客户付款已结清,附带费用明细 | 仅合作伙伴 |
| payment.refunded | 某笔付款已退款 | 仅合作伙伴 |
投递内容
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-LinkProfit-Event: link.created
X-LinkProfit-Delivery: dlv_8Yq2…
X-LinkProfit-Signature: t=1755081600,v1=5f8a2c…
{
"id": "dlv_8Yq2…",
"type": "link.created",
"created_at": "2026-08-13T10:00:00.000Z",
"data": { "id": "lnk_…", "domain": "go.your-brand.com", "slug": "x7f2q1z", "url": "https://example.com" }
}
请在 10 秒内返回任意 2xx 响应。其他任何情况——包括超时——都算作失败,并会安排重试。投递可能乱序到达,极少数情况下还会重复到达两次:请按 id 去重。
验证签名
签名是用你的端点密钥对 `${t}.${rawBody}` 计算的 HMAC-SHA256,并以十六进制编码。请始终针对原始请求体进行验证,且要在任何 JSON 解析之前完成;同时拒绝早于 5 分钟的时间戳。
import { createHmac, timingSafeEqual } from "node:crypto";
function verifySignature(secret, rawBody, header, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((piece) => piece.split("=", 2)),
);
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && timingSafeEqual(a, b);
}
import hashlib
import hmac
import time
def verify_signature(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
parts = dict(piece.split("=", 1) for piece in header.split(",") if "=" in piece)
timestamp, signature = parts.get("t"), parts.get("v1")
if not timestamp or not signature:
return False
if abs(time.time() - int(timestamp)) > tolerance:
return False
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
重试与失败状态
第一次尝试是立即进行的。失败后按固定的时间表重试:1 分钟 → 5 分钟 → 30 分钟 → 2 小时 → 12 小时。第五次重试之后,该端点会被标记为 failing,不再接收新事件,并且所有者会收到一封邮件。
修复端点之后,在控制台中重新启用它,或者调用 PATCH /v1/webhooks/{id} 并传入 {"status": "active"}。错过的事件不会自动重放——投递日志(GET /v1/webhooks/{id}/deliveries 或控制台)会展示每一条载荷,方便你自行核对补齐。
一次成功投递会重置连续失败的计数。
测试
发送测试按钮(或 POST /v1/webhooks/{id}/test)会通过与生产事件完全相同的链路,投递一条真实的、带签名的事件,其中 "data": { "test": true }——投递日志会显示你的端点返回的响应。