开发者 Webhook 接入指南

面向:接入本平台的第三方开发者 本文档说明如何开通开发者能力、配置回调地址、接收事件推送并校验签名。


一、开通开发者能力

  1. 联系平台管理员,为你的账号开通"开发者"权限(后台操作)。
  2. 开通后,登录 PC 端(网页端),进入「开发者中心」入口。
  3. 进入「开发者中心」,完成以下配置:
    • 回调 URL:填写你的服务端接收地址(必须为 http://https:// 公网地址)。
    • 订阅事件:勾选需要接收的事件类型。
    • 签名密钥:点击「重置密钥」,系统会生成一个 32 字节随机密钥(64 位十六进制)。该密钥仅展示一次,请立即妥善保存,丢失后需重新生成(旧密钥随即失效)。

访问入口说明:开发者中心仅在 PC 端(网页端)提供,不提供微信端(公众号/小程序)入口。开发者请在 PC 浏览器中登录后访问。


二、接收事件推送

2.1 请求方式

当订阅的事件发生时,平台会向你的回调 URL 发起 HTTP POST 请求:

  • Content-Type: application/json
  • 请求体为 JSON,字符集 UTF-8
  • 平台连接/总超时 5 秒

2.2 请求头

Header 说明
X-Server-Signature 请求签名,格式 sha256=<十六进制小写>
X-Server-Timestamp 发起请求时的 Unix 秒级时间戳
X-Server-Event 事件类型(见下)
User-Agent WxServer-Webhook/1.0

2.3 事件类型

X-Server-Event 含义
App\Events\EventIotNewRecord 设备产生新的报警/操作记录
App\Events\EventIotRamSync 设备状态同步(在线/离线、布撤防状态变化)

三、推送数据结构

3.1 统一外层结构

{
  "event": "App\\Events\\EventIotNewRecord",
  "device_id": 12345,
  "sent_at": 1723712400,
  "data": { }
}
字段 类型 说明
event string 事件类型
device_id int 设备 ID
sent_at int 平台发送时间(Unix 秒)
data object 事件数据,结构随事件类型不同

说明:目前推送不包含 event_id 字段(唯一 ID 为可选增强,默认未实装)。如需幂等去重,可结合 sent_at + 事件内容自行处理。

3.2 EventIotNewRecorddata

{
  "device_id": 12345,
  "records": [
    {
      "host_device_id": 12345,
      "timestamp": 1723712400,
      "oper_type": 4,
      "oper_group_id": 0,
      "oper_id": 3,
      "event": 1400
    }
  ]
}

records 为本次新增的记录数组。字段说明:

字段 类型 说明
host_device_id int 设备 ID
timestamp int 记录发生时间(Unix 秒)
oper_type int 操作来源类型(见 3.4 节枚举)
oper_group_id int 操作所属分组 ID
oper_id int 操作者 ID
event int 事件码(见 3.5 节枚举)

3.3 EventIotRamSyncdata

{
  "device_id": 12345,
  "type": "online",
  "flags": {
    "alarm": 0,
    "bypass": 0,
    "setting": 0,
    "duress": 0,
    "pairing": 0,
    "tamper": 0,
    "ac_lost": 0,
    "bt_low": 0,
    "bt_percent": 100,
    "signal_strength": 3
  },
  "status": [
    { "group_id": 0, "status": 1 }
  ],
  "delayed_exit_settings": null,
  "current_operator": null
}
字段 类型 说明
type string online(在线)/ offline(离线)
flags object 设备状态位,字段含义见 3.6 节
status array|null 各防区状态列表(见 3.7 节),离线时为 null
delayed_exit_settings object|null 延时退出设置({delay: int}),离线时为 null
current_operator object|null 当前操作者信息

3.4 oper_type 操作来源类型枚举

常量 含义
0 NONE 无效
1 KEYPAD 键盘/面板按键
2 REMOTE 物理遥控器
3 SENSOR 物理传感器(探测器)
4 HOST 主机自身
5 TIMER 定时器
6 APP APP 用户
7 ADMIN 管理员
8 WEB Web 端
9 CSR 接警中心
10 _3RD 第三方
11 RELAY IoT 设备中继

3.5 event 事件码枚举

事件码遵循安定宝(ADEMCO)报警协议,并包含平台自定义事件。

主机状态事件:

常量 含义
3400 ARM_AWAY 离家布防
1400 DISARM 撤防
3456 ARM_STAY 留守布防
1456 ARM_STAY_1456 留守布防

防区报警事件:

常量 含义
1120 EMERGENCY 紧急报警
1130 BURGLAR 盗警
1134 DOOR_RING 门铃
1110 FIRE 火警
1121 DURESS 胁迫
1151 GAS 燃气泄漏
1113 WATER 水泄漏
1137 TAMPER 主机防拆
1383 SENSOR_TAMPER 防区防拆
1570 BYPASS 防区旁路
1574 SYSTEM_BYPASS 系统旁路

防区报警恢复事件:

常量 含义
3120 EMERGENCY_RECOVER 紧急恢复
3130 BURGLAR_RECOVER 盗警恢复
3134 DOOR_RING_RECOVER 门铃恢复
3110 FIRE_RECOVER 火警恢复
3121 DURESS_RECOVER 胁迫恢复
3151 GAS_RECOVER 燃气恢复
3113 WATER_RECOVER 水泄漏恢复
3137 TAMPER_RECOVER 主机防拆恢复
3383 SENSOR_TAMPER_RECOVER 防区防拆恢复
3570 BYPASS_RECOVER 防区旁路解除
3574 SYSTEM_BYPASS_RECOVER 系统旁路解除

防区异常事件:

常量 含义
1301 AC_BROKEN 主机 AC 掉电
1302 LOW_BATTERY 低电
1311 BAD_BATTERY 坏电
1387 SOLAR_DISTURB 光扰
1381 DISCONNECT 失效
1393 LOST 失联
1384 POWER_EXCEPTION 电源故障
1380 OTHER_EXCEPTION 其他故障

防区异常恢复事件:

常量 含义
3301 AC_RECOVER 主机 AC 复电
3302 LOW_BATTERY_RECOVER 低电恢复
3311 BAD_BATTERY_RECOVER 坏电恢复
3387 SOLAR_DISTURB_RECOVER 光扰恢复
3381 DISCONNECT_RECOVER 失效恢复
3393 LOST_RECOVER 失联恢复
3384 POWER_EXCEPTION_RECOVER 电源故障恢复
3380 OTHER_EXCEPTION_RECOVER 其他故障恢复
3100 CLEAR_EXCPTION 清除异常指示

平台自定义事件:

常量 含义
1485 SERIAL_485_DIS 485 断开
3485 SERIAL_485_RECOVER 485 连接
1700 CONN_HANGUP 链路挂起
3700 CONN_RECOVER 链路恢复
1701 DISARM_PWD_ERR 撤防密码错误
1702 SUB_MACHINE_SENSOR_EXCEPTION 分机探头异常
3702 SUB_MACHINE_SENSOR_RECOVER 分机探头恢复
1703 SUB_MACHINE_POWER_EXCEPTION 分机电源异常
3703 SUB_MACHINE_POWER_RECOVER 分机电源恢复
1704 COM_PASSTHROUGH 串口透传
2704 ENTER_SET_MODE 进入设置状态
3704 EXIT_SET_MODE 退出设置状态
1705 QUERY_SUB_MACHINE 查询分机信息
1706 WRITE_TO_MACHINE 写入主机信息
1707 I_AM_NET_MODULE 主机类型--网络模块
1709 PHONE_USER_SOS 手机用户 SOS
1711 PHONE_USER_CANCLE_ALARM 手机用户消警
1712 ENTER_SETTING_MODE 主机进入设置状态
3712 EXIT_SETTING_MODE 主机退出设置状态
1710 RESTORE_FACTORY_SETTINGS_710 主机恢复出厂设置
1713 RESTORE_FACTORY_SETTINGS 主机恢复出厂设置
1944 OFFLINE 主机断线
1946 ONLINE 主机上线

3.6 flags 状态位说明

状态位 类型 取值 含义
alarm boolean 0/1 是否报警中
bypass boolean 0/1 是否旁路中
setting boolean 0/1 是否设置中
duress boolean 0/1 是否胁迫状态
pairing integer 0/1/2 0 未配对;1 正在配对遥控器;2 正在配对探测器
tamper boolean 0/1 是否防拆
ac_lost boolean 0/1 AC 电源是否掉电
bt_low boolean 0/1 电池是否低电
bt_percent integer 0-100 电池电量百分比
signal_strength integer 0-31 信号强度等级(百分比 = value/31*100

3.7 status 防区状态值

常量 含义
0 ARM_AWAY 离家布防
1 ARM_STAY 留守布防
2 DISARM 撤防
3 SETTING 设置中
255 INVALID 无效

四、验签(校验请求来自平台)

强烈建议:收到请求后务必先验签,确认请求确实来自本平台,防止伪造。

4.1 签名算法

signature = HMAC-SHA256( raw_body_string, secret )

其中:

  • raw_body_stringHTTP 请求的原始 body 字节串(原样字符串,不要重新解析/重排 JSON)。
  • secret:你在开发者中心生成的签名密钥。
  • 结果为二进制,需转成十六进制小写字符串。

4.2 校验步骤

  1. 取出请求头 X-Server-Signature,去掉 sha256= 前缀,得到 received
  2. 取出请求头 X-Server-Timestamp
  3. 校验时间戳:abs(当前时间 - X-Server-Timestamp) <= 300(防重放,窗口建议 300 秒)。
  4. 用原始 body 与你的 secret 计算本地签名 local
  5. 常量时间比较:hash_equals($local, $received),不相等则拒绝。

4.3 示例代码(PHP)

// 读原始 body(务必用 php://input,不要用 $_POST 或 json_decode 后再 encode)
$rawBody = file_get_contents('php://input');

$secret = getenv('WEBHOOK_SECRET'); // 你的密钥

$received = substr($_SERVER['HTTP_X_SERVER_SIGNATURE'] ?? '', 7); // 去掉 "sha256="
$timestamp = (int)($_SERVER['HTTP_X_SERVER_TIMESTAMP'] ?? 0);

// 1. 防重放
if (abs(time() - $timestamp) > 300) {
    http_response_code(401);
    exit('timestamp expired');
}

// 2. 计算本地签名
$local = hash_hmac('sha256', $rawBody, $secret);

// 3. 常量时间比较
if (!hash_equals($local, $received)) {
    http_response_code(401);
    exit('invalid signature');
}

// 验签通过,处理业务
$payload = json_decode($rawBody, true);
// ...

4.4 示例代码(Node.js)

const crypto = require('crypto');

// 以 Express 为例,需在 bodyParser.json 之前读取原始 body,
// 或使用 express.raw({ type: '*/*' }) 保持原始字节串
const secret = process.env.WEBHOOK_SECRET;
const rawBody = req.rawBody; // 原始 body 字符串
const received = (req.headers['x-server-signature'] || '').replace(/^sha256=/, '');
const timestamp = parseInt(req.headers['x-server-timestamp'] || '0', 10);

// 1. 防重放
if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
  return res.status(401).send('timestamp expired');
}

// 2. 计算本地签名
const local = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

// 3. 常量时间比较
if (!crypto.timingSafeEqual(Buffer.from(local), Buffer.from(received))) {
  return res.status(401).send('invalid signature');
}

// 验签通过
const payload = JSON.parse(rawBody);

注意:Node.js 中若使用 express.json() 会重新序列化 body,导致原始字节串丢失。请使用 express.raw({ type: 'application/json' }) 或手动拼接原始 body。

4.5 示例代码(Python)

import hmac
import hashlib
import time
import os

secret = os.environ['WEBHOOK_SECRET'].encode('utf-8')
raw_body = request.get_data()  # Flask 原始 body 字节串,勿用 json 解析后再编码

received = request.headers.get('X-Server-Signature', '').replace('sha256=', '')
try:
    timestamp = int(request.headers.get('X-Server-Timestamp', '0'))
except ValueError:
    return 'invalid timestamp', 401

# 1. 防重放
if abs(time.time() - timestamp) > 300:
    return 'timestamp expired', 401

# 2. 计算本地签名
local = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()

# 3. 常量时间比较
if not hmac.compare_digest(local, received):
    return 'invalid signature', 401

# 验签通过
payload = request.get_json()

4.6 示例代码(Go)

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "strconv"
    "strings"
    "time"
)

func verifyWebhook(w http.ResponseWriter, r *http.Request) bool {
    secret := []byte(os.Getenv("WEBHOOK_SECRET"))

    // 1. 读取原始 body(注意:只能读取一次)
    rawBody, _ := io.ReadAll(r.Body)

    received := strings.TrimPrefix(r.Header.Get("X-Server-Signature"), "sha256=")
    timestamp, _ := strconv.ParseInt(r.Header.Get("X-Server-Timestamp"), 10, 64)

    // 2. 防重放
    if abs64(time.Now().Unix()-timestamp) > 300 {
        http.Error(w, "timestamp expired", http.StatusUnauthorized)
        return false
    }

    // 3. 计算本地签名
    mac := hmac.New(sha256.New, secret)
    mac.Write(rawBody)
    local := hex.EncodeToString(mac.Sum(nil))

    // 4. 常量时间比较
    if !hmac.Equal([]byte(local), []byte(received)) {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return false
    }

    return true
}

func abs64(x int64) int64 {
    if x < 0 {
        return -x
    }
    return x
}

4.7 开箱即用示例(Python 完整接收器)

如果只想快速验证推送能否正常接收,可直接使用平台提供的测试接收器 webhook_receiver.py(需 Python 3):

  1. 下载接收器:webhook_receiver.py

  2. 安装依赖:

    pip install flask flask-cors
  3. 配置密钥(三选一,优先级从高到低):

    # 方式一:命令行参数
    python3 webhook_receiver.py '<你的签名密钥>'
    
    # 方式二:环境变量
    export WEBHOOK_SECRET='<你的签名密钥>'
    python3 webhook_receiver.py
    
    # 方式三:本地 .env 文件(在脚本同目录创建 .env,写入 WEBHOOK_SECRET=...)
    python3 webhook_receiver.py

    监听端口默认为 8080,可通过第二个参数修改: python3 webhook_receiver.py '<密钥>' 9000

  4. 在开发者中心把回调地址指向该服务(需公网可达,或用内网穿透工具)。

该接收器开箱即用,具备以下能力:

  • 验签(HMAC-SHA256 + 常量时间比较);
  • 防重放(时间戳窗口 300 秒);
  • 打印完整事件(请求头、原始 body、解析后的 JSON 及两类事件的中文解释);
  • 验签通过后返回 200,验签失败返回 401,符合平台的响应要求。

提示:未配置密钥时,接收器会跳过验签仅打印,方便本地联调;正式接入请务必配置密钥并开启验签。


五、响应要求

  • 平台认为以下情况为「成功」,会重置你的失败计数:
    • HTTP 状态码 2xx
  • 其他状态码或网络异常视为「失败」。
  • 请尽量在 5 秒内返回响应,平台连接超时为 5 秒。

六、幂等与去重

平台可能因网络重试等原因重复投递同一事件。由于当前推送不携带 event_id,建议:

  • 在业务侧自行生成去重键(如 sent_at + 事件内容摘要);
  • 对重复事件直接返回成功,不重复处理业务。

七、失败与熔断

  • 若你的回调地址连续多次(默认 5 次)返回失败或超时,平台会自动禁用你的 webhook,停止推送。
  • 禁用后,你需要在开发者中心修复回调地址并手动重新启用(或联系管理员)。
  • 请确保回调地址长期可用、稳定响应。

八、安全须知

  1. 妥善保管签名密钥:密钥仅生成时展示一次,请勿提交到代码仓库、日志或前端。
  2. 回调地址必须是公网地址:平台会校验 URL 并拒绝指向内网/本机地址的回调,以保障平台安全。
  3. 回调地址仅接收你授权设备的事件:平台只会将你本人名下设备产生的事件推送给你,不会推送其他用户的数据。
  4. 建议使用 HTTPS:签名已保证内容完整性,但 HTTPS 可进一步保证传输机密性。

九、FAQ

Q:为什么密钥不能再次查看? A:出于安全考虑,密钥只展示一次。若遗失,请在开发者中心重新生成,旧密钥立即失效,请同步更新你的验签配置。

Q:收不到推送怎么办? A:依次检查:① 是否已开通开发者权限;② 是否勾选了对应事件;③ 回调地址是否公网可达、是否被熔断禁用;④ 服务端日志中验签/超时情况。

Q:一次事件会推送多条吗? A:一次业务事件对应一次推送,但网络重试可能造成重复,请按 sent_at + 事件内容自行去重。

Q:EventIotRamSync 推送频率会不会很高? A:该事件在设备状态(在线/离线、布撤防、flags 变化)时触发,可能较频繁。默认该事件订阅为关闭,请按需开启。


十、免责声明

  1. 数据责任:本平台仅作为报警/操作数据的转发通道,将事件原样推送到你配置的回调地址。开发者应对回调地址所接收数据的合法性、准确性、完整性与合规性自行负责,并自行确保其符合适用的法律法规与监管要求。

  2. 安全责任:请务必采用 HTTPS、妥善保管签名密钥,并对回调服务做好鉴权、限流与防攻击防护。因密钥泄露、回调地址被恶意调用或数据在传输/存储环节泄露所造成的损失,由开发者自行承担。

  3. 服务可用性:推送服务按「现状」提供,平台不承诺其不中断、无错误或持续可用。因网络故障、设备离线、平台维护或不可抗力等因素导致的推送延迟、丢失或失败,平台不承担任何责任。

  4. 及时性:报警类推送仅作为辅助通知手段,不能替代实时监控、现场值守或任何其他专业安防措施。开发者及相关方不得仅依赖本推送做出安全、财产或人身相关的处置决策,由此产生的后果由使用方自行承担。

  5. 第三方行为:开发者通过本平台向第三方(如接警中心、通知服务等)转发数据,属开发者自身行为。平台对第三方的数据使用、存储或泄露不承担任何责任。

  6. 合规与资质:开发者应自行取得开展相关业务所需的全部资质与授权,并保证不利用本服务从事任何违法违规活动。因违反本条规定引发的一切法律责任,由开发者独立承担。

  7. 条款变更:平台保留随时修改本接入规则或暂停、终止推送服务的权利,恕不另行逐一通知。