Appearance
雷鸟跑腿 状态回调验签说明
版本:1.0
日期:2026-10-23
适用:雷鸟跑腿 订单状态回调(X-Sign)
1. 概述
订单状态变更时,我方向三方平台配置的 callbackUrl 发送 POST 请求,并在请求头携带 X-Callback-Id、X-Timestamp、X-Sign。
X-Sign算法:HMAC-SHA256(appSecret, 签名原文),结果小写 hex。appSecret由平台注册时分配(platform_config.app_secret)。- 签名覆盖:回调 ID、订单 ID、状态、时间戳,以及请求体的 SHA-256。
2. 请求
Method: POST
Content-Type: application/json
请求头:
| Header | 说明 |
|---|---|
X-Callback-Id | 回调ID,与 body.callbackId 一致,用于幂等去重 |
X-Timestamp | Unix 时间戳(秒) |
X-Sign | HMAC-SHA256 签名(小写 hex) |
请求体字段:
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
callbackId | String | 否 | 回调ID |
orderId | String | 否 | 我方内部订单ID(字符串) |
orderNo | String | 否 | 我方内部订单号 |
thirdOrderNo | String | 是 | 三方订单号(原样回传) |
status | Integer | 否 | 订单状态码 |
statusDesc | String | 否 | 状态描述 |
riderName | String | 是 | 骑手姓名,未接单时为 null |
riderPhone | String | 是 | 骑手电话,未接单时为 null |
callbackTime | String | 否 | 回调时间 yyyy-MM-dd HH:mm:ss |
状态码:
| 状态码 | 说明 |
|---|---|
| 0 | 待支付 |
| 1 | 已取消 |
| 2 | 已支付待接单 |
| 3 | 已接单待到店 |
| 4 | 已到店待取件 |
| 5 | 已取件配送中 |
| 6 | 已送达 |
| 7 | 已完成(已评价) |
| 8 | 异常 |
| 9 | 已退款 |
| 10 | 返还中 |
| 11 | 已返还 |
回调可能推送 0~11 中任意状态,请勿只处理 1~7。
3. 签名原文
callbackId{callbackId}orderId{orderId}status{status}timestamp{timestamp}bodyHash{bodyHash}生成步骤:
- 对收到的原始 body 字节做 SHA-256,转小写 hex,得到
bodyHash。 - 取请求头
X-Callback-Id、X-Timestamp和 body 中的status(整数转字符串)。 - 按上述模板拼接(无分隔符、无 URL 编码)。
HMAC-SHA256(appSecret, 拼接串),转小写 hex,与X-Sign比较(常量时间比较)。
4. 完整样例
请求头
Content-Type: application/json
X-Callback-Id: CB_12345_3_1698888888000
X-Timestamp: 1761200000
X-Sign: 3e532b8bc09c6d5178cc3b464118834b1409279c63bad27e632246d0135aa966Body(原始字符)
json
{"callbackId":"CB_12345_3_1698888888000","orderId":"12345","orderNo":"ORD202610230001","thirdOrderNo":"THIRD202610230001","status":3,"statusDesc":"已接单待到店","riderName":"张三","riderPhone":"13800000000","callbackTime":"2026-10-23 12:00:00"}中间值
appSecret = test_app_secret_please_replace (示例值,实际以平台分配为准)
bodyHash = 5fbb38de8e07f4aa63f89c17144647caf4eb96a3fabb7e282445b2629e3684ac
timestamp = 1761200000签名原文
callbackIdCB_12345_3_1698888888000orderId12345status3timestamp1761200000bodyHash5fbb38de8e07f4aa63f89c17144647caf4eb96a3fabb7e282445b2629e3684acX-Sign
3e532b8bc09c6d5178cc3b464118834b1409279c63bad27e632246d0135aa9665. 验签代码示例
Java
java
String bodyHash = sha256Hex(rawBodyBytes); // 小写 hex
String plain = "callbackId" + callbackId
+ "orderId" + orderId
+ "status" + status
+ "timestamp" + timestamp
+ "bodyHash" + bodyHash;
String expected = hmacSha256Hex(appSecret, plain); // 小写 hex
boolean ok = java.security.MessageDigest.isEqual(
expected.getBytes(java.nio.charset.StandardCharsets.UTF_8),
sign.getBytes(java.nio.charset.StandardCharsets.UTF_8));Node.js
js
const crypto = require('crypto');
const bodyHash = crypto.createHash('sha256').update(rawBody).digest('hex');
const plain = `callbackId${callbackId}orderId${orderId}status${status}`
+ `timestamp${timestamp}bodyHash${bodyHash}`;
const expected = crypto.createHmac('sha256', appSecret).update(plain, 'utf8').digest('hex');
const ok = expected.length === sign.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sign));PHP
php
$bodyHash = hash('sha256', $rawBody);
$plain = "callbackId{$callbackId}orderId{$orderId}status{$status}"
. "timestamp{$timestamp}bodyHash{$bodyHash}";
$expected = hash_hmac('sha256', $plain, $appSecret);
$ok = hash_equals($expected, $sign);Python
python
import hashlib, hmac
body_hash = hashlib.sha256(raw_body).hexdigest()
plain = (f"callbackId{callback_id}orderId{order_id}status{status}"
f"timestamp{timestamp}bodyHash{body_hash}")
expected = hmac.new(app_secret.encode(), plain.encode(), hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, sign)6. 响应、重试与幂等
- 返回 HTTP 2xx(建议 body
{"code":0})即视为成功;我方只判断 HTTP 状态码。 - 失败重试:最多 5 次,间隔 1 / 5 / 15 / 30 / 60 分钟;首次推送后超过 1 小时停止。
- 超过重试次数后仅记录失败日志,不再推送。
- 按
X-Callback-Id幂等去重;重试时callbackId不变,但X-Timestamp会重新生成,X-Sign随之变化,请每次重新验签。
7. 常见问题
- bodyHash 必须基于原始字节:不要反序列化后重新序列化再哈希,字段顺序、空格、
null都会变化。 - 统一 UTF-8;签名结果均为小写 hex。
- 时间戳为秒级;建议校验
|now - timestamp| ≤ 300s防重放。 - 签名参数只有 4 个:
callbackId、orderId、status、timestamp,不要加入其他 body 字段。 - bodyHash 只拼接一次,位于原文末尾。
X-Callback-Id与body.callbackId应一致,可作一致性校验。