Webhook 事件

检测数据落库后,晓葆开放平台异步 POST plugin.report.ready 到宿主配置的 Webhook URL。 这是唯一的 Webhook 事件,订阅此事件即可获取全部检测类型的结果。

推送方式

说明
协议HTTPS POST,请求体 JSON
触发时机插件检测数据落库后异步触发(ECG / BCA 算法异步完成后触发)
超时判定宿主接口响应时间 > 10s,视为失败触发重试
Webhook URL 配置在晓葆开发者门户 → 应用设置中配置
业务处理耗时较长时,先返回 HTTP 200,再异步处理,避免触发重试。

请求 Header

POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Webhook-Id:        <UUID>           # 本次投递唯一 ID,重试时保持不变(幂等键)
X-Webhook-Event:     plugin.report.ready
X-Webhook-Signature: <HMAC-SHA256>    # 验签用
X-Webhook-Timestamp: <Unix 秒>

验签

signature = HMAC_SHA256(webhookSecret, X-Webhook-Timestamp + "." + rawBody)
验签时使用原始请求体字节(rawBody),不要先 JSON.parse

Node.js 示例

const crypto  = require('crypto')
const express = require('express')
const app     = express()

app.post('/webhook/xiaobao', express.raw({ type: '*/*' }), async (req, res) => {
  const deliveryId = req.headers['x-webhook-id']
  const timestamp  = req.headers['x-webhook-timestamp']
  const signature  = req.headers['x-webhook-signature']
  const rawBody    = req.body.toString('utf8')  // 原始字节转字符串,不 JSON.parse

  // 1. 防重放:拒绝 5 分钟前的请求
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(400).end()
  }

  // 2. 验签
  const expected = crypto
    .createHmac('sha256', process.env.XIAOBAO_WEBHOOK_SECRET)
    .update(timestamp + '.' + rawBody)
    .digest('hex')
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).end()
  }

  // 3. 幂等:以 X-Webhook-Id 去重
  if (await isDelivered(deliveryId)) return res.json({ code: 0 })

  // 4. 处理事件
  const payload = JSON.parse(rawBody)
  if (payload.event === 'plugin.report.ready') {
    const { measurement_no, third_user_id, report_type } = payload
    await saveReport({ measurementNo: measurement_no, thirdUserId: third_user_id, reportType: report_type })
  }

  await markDelivered(deliveryId)
  res.json({ code: 0 })  // 必须在 10s 内响应
})

PHP 示例

function verifyWebhook(string $rawBody, array $headers, string $secret): bool {
    $timestamp = $headers['X-Webhook-Timestamp'] ?? '';
    $received  = $headers['X-Webhook-Signature'] ?? '';

    if (abs(time() - (int)$timestamp) > 300) {
        return false;  // 防重放
    }

    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $received);
}

plugin.report.ready

检测报告就绪后触发,同步设备数据落库即触发;ECG / BCA 异步算法完成后触发。

Payload 字段

字段类型说明
eventstring固定值 plugin.report.ready
delivery_idstring幂等键,与 X-Webhook-Id 同值
app_idint晓葆应用 ID
measurement_nostring检测编号,与插件出参 measureId 对应
third_user_idstring|null宿主跳转时传入的第三方用户 ID
xiaobao_user_idint|null若宿主传了手机号完成绑定则有值,否则 null
report_typestring见下方枚举
report_urlstring|null当前版本为 null,后续版本补充
metadatastring|null宿主跳转时传入的自定义字符串,未传则为 null
occurred_atstringISO 8601,如 2026-07-01T15:30:15+08:00

report_type 枚举

report_type对应设备deviceKey
blood_pressure多合一生化仪(血压)HOME_MULTI_BIO_MONITOR
blood_glucose多合一生化仪(血糖)HOME_MULTI_BIO_MONITOR
spo2指环血氧仪HOME_SPO2_RING
ecg心电记录仪HEARTLOG_ECG_RECORDER
bca人体成分秤BODY_COMPOSITION_ANALYSIS

Payload 示例

{
  "event": "plugin.report.ready",
  "delivery_id": "550e8400-e29b-41d4-a716-446655440000",
  "app_id": 42,
  "measurement_no": "PLG-20260701153012-ABCD12",
  "third_user_id": "user_abc123",
  "xiaobao_user_id": null,
  "report_type": "blood_pressure",
  "report_url": null,
  "metadata": null,
  "occurred_at": "2026-07-01T15:30:15+08:00"
}
measurement_no 与插件出参 measureId 完全一致,可用于关联本地检测记录。third_user_id 即宿主跳转时传入的用户 ID,可直接识别用户。

重试策略

次数间隔
第 1 次重试1 分钟后
第 2 次重试5 分钟后
第 3 次重试30 分钟后
第 4 次重试2 小时后
第 5 次重试6 小时后

5 次全部失败后写入死信队列,不再自动重试。请务必以 X-Webhook-Id 作为幂等键防止重复处理。