Skip to content

Webhooks

M Express 会向你的接收端发送配送事件。投递至少一次,并可能乱序到达,因此接收、验签、持久 去重、排序、确认和异步处理共同构成完整的集成契约。

接收前准备

准备能接收 HTTPS POST、在解析 JSON 前保留原始字节、并持久保存事件记录的服务端接收器。 按 key ID 管理签名密钥;密钥不得进入浏览器、移动端、日志或支持截图。

接收端实现流程

  1. 在解析 JSON 前,读取完全未改动的原始请求字节。
  2. 根据 key ID 选择当前有效的签名密钥。
  3. 对原始字节计算并校验 HMAC-SHA256 签名。
  4. 在产生副作用前,持久记录 event_id
  5. 对重复的 event_id 不再产生第二次业务处理。
  6. 快速返回正常大小的 2xx,再执行较慢的下游工作。

客户可见状态应按 payload 的 scanned_at 排列,不应按请求抵达的先后排序。先持久记录 event_id,尽快确认,再异步执行较慢的下游工作。

读取请求并验签

请求包含 Content-Type: application/json,以及以下四个 webhook 请求头:

Header作用
X-Delivery-Webhook-Id用于去重的事件标识。
X-Delivery-Webhook-Timestamp参与 HMAC 输入的 Unix epoch 秒数。
X-Delivery-Webhook-Key-Id指明应使用哪个有效签名密钥。
X-Delivery-Webhook-Signature带有 v1= 前缀的 HMAC-SHA256 值。
http
POST <your receiver URL>
Content-Type: application/json
X-Delivery-Webhook-Id: evt_...
X-Delivery-Webhook-Timestamp: 1751422800
X-Delivery-Webhook-Key-Id: whk_...
X-Delivery-Webhook-Signature: v1=...

HMAC-SHA256 的准确输入为:

text
<timestamp>.<event_id>.<raw_body>

raw_body 是未改动的请求数据。无效签名必须在解析或处理 JSON 前被拒绝。以下示例是契约示例, 展示验签操作而不是完整的 HTTP 接收器。

js
import crypto from "node:crypto";

function validWebhook({ timestamp, eventId, signature, secret, rawBody }) {
  const input = Buffer.concat([
    Buffer.from(`${timestamp}.${eventId}.`, "utf8"),
    rawBody,
  ]);
  const expected = `v1=${crypto.createHmac("sha256", secret).update(input).digest("hex")}`;
  const received = Buffer.from(signature, "utf8");
  const wanted = Buffer.from(expected, "utf8");
  return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}
python
import hashlib
import hmac

def valid_webhook(timestamp, event_id, signature, secret, raw_body):
    signing_input = b".".join((
        timestamp.encode("ascii"), event_id.encode("utf-8"), raw_body,
    ))
    expected = "v1=" + hmac.new(
        secret.encode("utf-8"), signing_input, hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

安全确认

接收端结果投递结果
任意正常大小的 2xx已确认。
4084254295xx可重试。
3xx 或其他 4xx永久失败。
任意状态下响应体大于 8192 字节永久失败。

在执行副作用前先持久记录 event_id,再在耗时的下游处理前返回确认。这样即使 worker 延迟或重启, 重试行为仍然安全。

事件目录

webhook.test 用于验收你的接收端;其余条目都是业务事件。pod.completed 只是事件名,不通过 本公开指南提供 POD 媒体。

EventCode
webhook.testEVT000
shipment.createdEVT010
shipment.updatedEVT020
package.pickup_scannedEVT100
package.inbound_scannedEVT200
package.loadedEVT300
package.delivery_scannedEVT350
pod.completedEVT400
shipment.deliveredEVT410
shipment.partially_deliveredEVT420
shipment.exceptionEVT500
shipment.return_requestedEVT600
shipment.returned_to_senderEVT700

验证清单

  • [ ] 在 JSON 解析前保留原始请求字节。
  • [ ] 四个 header 齐全,并用 <timestamp>.<event_id>.<raw_body> 进行常量时间 HMAC 比较。
  • [ ] 在产生业务副作用前已持久、幂等地记录 event_id
  • [ ] 客户可见顺序使用 scanned_at,而非抵达顺序。
  • [ ] 确认响应为 2xx 且响应体不超过 8192 字节。
  • [ ] 上线前已成功完成 webhook.test / EVT000 验收。

下一步

回到开始接入完成上线检查,或阅读追踪 API实现客户查询。

M Express 服务端集成指南