Webhooks
Updated:
除了轮询外,您还可以在 HTTPS 端点上接收订单事件。Webhooks 传递的事件与事件顺序. 无论是否启用 webhook,事件日志始终可用,因此您可以随时改用轮询方式。
设置
响应结果如下
webhook_secret(whsec_…) 仅需设置一次。将其保存:它会为每个 webhook 签名。修改
callback_url生成一个新的密钥。之前的密钥将在24小时内继续为 webhooks 签名:在此期间X-PS-Signature带有两个签名。
请求
属性 | 价值 |
|---|---|
方法 |
|
标题 |
|
正文 | 一个事件,例如事件顺序; |
成功 | 任何 |
交付 | 至少出现一次,顺序不固定。通过以下方式去除重复项: |
POST /hooks/proxy HTTP/1.1
Host: agent.example.com
Content-Type: application/json; charset=utf-8
User-Agent: ProxySeller-Webhooks/1
X-PS-Timestamp: 1762077838
X-PS-Signature: v1=5f0c2a…9e
{"event_id":"evt_01JZ8QA2C4E6G8J0K2M4P6R8T0","type":"order.active","created_at":"2026-11-02T10:03:58Z","api_version":"v1","order_id":"ord_01JZ8Q7K5N2R4T6V8X0Z2B4D6F","wallet":"0x857b06519e91e3a54538791bdbb0e22373e36b66","attempt":1,"data":{"lines":[{"line_id":"1","status":"active","delivered":2,"qty":2}],"expires_at":"2026-12-02T23:59:59Z"}}验证签名
X-PS-Signature: v1=hex(HMAC-SHA256(key, timestamp + "." + raw_body))
key— 全文的字节(UTF-8)webhook_secret字符串,包括whsec_前缀。timestamp— 的价值X-PS-Timestamp,Unix 秒。raw_body— 正文内容完全按收到的原样保留,未进行任何 JSON 解析。在秘密变更后的24小时内,页眉显示为
v1=<current>,v1=<previous>:若其中任何一项匹配,则接受该请求。若请求的时间戳与您的系统时间相差超过300秒,请拒绝该请求。
Python
import hashlib
import hmac
import time
def verify(secret: str, timestamp: str, signature_header: str, raw_body: bytes, tolerance: int = 300) -> bool:
if abs(time.time() - int(timestamp)) > tolerance:
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
candidates = [part.strip()[3:] for part in signature_header.split(",") if part.strip().startswith("v1=")]
return any(hmac.compare_digest(expected, candidate) for candidate in candidates)Node.js
import crypto from 'node:crypto';
function verify(secret, timestamp, signatureHeader, rawBody, toleranceSeconds = 300) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
return signatureHeader.split(',').map((s) => s.trim()).filter((s) => s.startsWith('v1='))
.some((s) => s.length === expected.length + 3
&& crypto.timingSafeEqual(Buffer.from(s.slice(3)), Buffer.from(expected)));
}重试
规则 | 价值 |
|---|---|
初稿 | 事件发生后至少30秒 |
重试 | 1 分钟、5 分钟、30 分钟、2 小时、6 小时、12 小时和 24 小时后:共 8 次尝试,耗时约 44.6 小时 |
过时 | 超过约45.6小时的事件将不再发送 |
断路器 | 连续失败3次后,该任务链中的其他事件将等待下一次尝试 |
禁用 | 如果某次事件的最后一次尝试失败,且自该事件创建以来该 URL 尚未接收任何 webhook,则将禁用向该 URL 的推送。订单本身不受影响。 |
若要启用已失效的链接,请重新发送该链接并附上更新顺序:同一URL每小时最多更新一次,新URL可随时更新。遗漏的事件始终位于事件顺序.
callback_url 规则
在报价发出时及PATCH。如有违反,将予以退稿400 invalid_request:
规则 | 详情 |
|---|---|
可打印的ASCII格式,最多2048个字符 |
|
|
|
URL中不得包含登录名或密码 |
|
无片段 |
|
仅限443端口 |
|
一个公共DNS名称,或一个规范的IPv4地址,或一个用方括号括起的IPv6地址 |
|
主机的所有地址均为公共地址 |
|
并非该服务本身的主机 |
|
每次交付前都会再次检查 DNS;请求仅发送至已通过检查的地址。