面向:接入本平台的第三方开发者 本文档说明如何开通开发者能力、配置回调地址、接收事件推送并校验签名。
http:// 或 https:// 公网地址)。访问入口说明:开发者中心仅在 PC 端(网页端)提供,不提供微信端(公众号/小程序)入口。开发者请在 PC 浏览器中登录后访问。
当订阅的事件发生时,平台会向你的回调 URL 发起 HTTP POST 请求:
Content-Type: application/json| Header | 说明 |
|---|---|
X-Server-Signature |
请求签名,格式 sha256=<十六进制小写> |
X-Server-Timestamp |
发起请求时的 Unix 秒级时间戳 |
X-Server-Event |
事件类型(见下) |
User-Agent |
WxServer-Webhook/1.0 |
| X-Server-Event | 含义 |
|---|---|
App\Events\EventIotNewRecord |
设备产生新的报警/操作记录 |
App\Events\EventIotRamSync |
设备状态同步(在线/离线、布撤防状态变化) |
{
"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+ 事件内容自行处理。
EventIotNewRecord 的 data{
"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 节枚举) |
EventIotRamSync 的 data{
"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 | 当前操作者信息 |
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 设备中继 |
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 | 主机上线 |
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) |
status 防区状态值| 值 | 常量 | 含义 |
|---|---|---|
0 |
ARM_AWAY | 离家布防 |
1 |
ARM_STAY | 留守布防 |
2 |
DISARM | 撤防 |
3 |
SETTING | 设置中 |
255 |
INVALID | 无效 |
强烈建议:收到请求后务必先验签,确认请求确实来自本平台,防止伪造。
signature = HMAC-SHA256( raw_body_string, secret )
其中:
raw_body_string:HTTP 请求的原始 body 字节串(原样字符串,不要重新解析/重排 JSON)。secret:你在开发者中心生成的签名密钥。X-Server-Signature,去掉 sha256= 前缀,得到 received。X-Server-Timestamp。abs(当前时间 - X-Server-Timestamp) <= 300(防重放,窗口建议 300 秒)。secret 计算本地签名 local。hash_equals($local, $received),不相等则拒绝。// 读原始 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);
// ...
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。
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()
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
}
如果只想快速验证推送能否正常接收,可直接使用平台提供的测试接收器
webhook_receiver.py(需 Python 3):
下载接收器:webhook_receiver.py
安装依赖:
pip install flask flask-cors
配置密钥(三选一,优先级从高到低):
# 方式一:命令行参数
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。
在开发者中心把回调地址指向该服务(需公网可达,或用内网穿透工具)。
该接收器开箱即用,具备以下能力:
200,验签失败返回 401,符合平台的响应要求。提示:未配置密钥时,接收器会跳过验签仅打印,方便本地联调;正式接入请务必配置密钥并开启验签。
2xx。平台可能因网络重试等原因重复投递同一事件。由于当前推送不携带 event_id,建议:
sent_at + 事件内容摘要);Q:为什么密钥不能再次查看? A:出于安全考虑,密钥只展示一次。若遗失,请在开发者中心重新生成,旧密钥立即失效,请同步更新你的验签配置。
Q:收不到推送怎么办? A:依次检查:① 是否已开通开发者权限;② 是否勾选了对应事件;③ 回调地址是否公网可达、是否被熔断禁用;④ 服务端日志中验签/超时情况。
Q:一次事件会推送多条吗?
A:一次业务事件对应一次推送,但网络重试可能造成重复,请按 sent_at + 事件内容自行去重。
Q:EventIotRamSync 推送频率会不会很高?
A:该事件在设备状态(在线/离线、布撤防、flags 变化)时触发,可能较频繁。默认该事件订阅为关闭,请按需开启。
数据责任:本平台仅作为报警/操作数据的转发通道,将事件原样推送到你配置的回调地址。开发者应对回调地址所接收数据的合法性、准确性、完整性与合规性自行负责,并自行确保其符合适用的法律法规与监管要求。
安全责任:请务必采用 HTTPS、妥善保管签名密钥,并对回调服务做好鉴权、限流与防攻击防护。因密钥泄露、回调地址被恶意调用或数据在传输/存储环节泄露所造成的损失,由开发者自行承担。
服务可用性:推送服务按「现状」提供,平台不承诺其不中断、无错误或持续可用。因网络故障、设备离线、平台维护或不可抗力等因素导致的推送延迟、丢失或失败,平台不承担任何责任。
及时性:报警类推送仅作为辅助通知手段,不能替代实时监控、现场值守或任何其他专业安防措施。开发者及相关方不得仅依赖本推送做出安全、财产或人身相关的处置决策,由此产生的后果由使用方自行承担。
第三方行为:开发者通过本平台向第三方(如接警中心、通知服务等)转发数据,属开发者自身行为。平台对第三方的数据使用、存储或泄露不承担任何责任。
合规与资质:开发者应自行取得开展相关业务所需的全部资质与授权,并保证不利用本服务从事任何违法违规活动。因违反本条规定引发的一切法律责任,由开发者独立承担。
条款变更:平台保留随时修改本接入规则或暂停、终止推送服务的权利,恕不另行逐一通知。