❮  集成

Webhook 规范

Entrixy 向您的地址发送开启或关闭命令并对其签名。本页介绍请求格式以及如何验证签名,以便在您自己的服务、智能家居或电路板上接收命令。

Webhook 是一种由您自己的系统而非我们的控制器执行开启的模式:Home Assistant、局域网中的继电器或您自己的服务器。Webhook 对象在两种模式下发送的都是同一个已签名请求。 从您的手机发送:由所有者的手机发送,地址和密钥始终不离开手机。 从 Entrixy 服务器发送:由我们的服务器发送,因此服务器保存地址和密钥。现成的接收端: curl, Flask, Home Assistant。

请求

每次点击时,Entrixy 发送一个 POST 到您的 webhook_url ,正文为 JSON:

POST <your webhook_url>
Content-Type: application/json

{
  "action":    "open",
  "object_id": 42,
  "timestamp": 1750000000,
  "nonce":     "0011223344556677",
  "signature": "9d7b58066094fa88..."
}
字段取值
action"open",对于“开启 / 关闭”对象还有 "close"
object_id该对象在您账户中的 id
timestamp发送时刻的 unix 时间,单位秒
nonce16 个十六进制字符(8 个随机字节),一次性
signatureHMAC-SHA256,见下文

“开启 / 关闭”对象根据最后已知的状态交替发送: close 若对象已开启则发送此命令,否则发送 open。服务器模式下由服务器决定,手机模式下由所有者的手机决定。

签名

被签名的是一个 规范化字符串 ,由四个以点号分隔的字段组成,而不是 JSON 正文本身,因此您不必逐字节复刻我们的序列化方式:

base = "<timestamp>.<nonce>.<action>.<object_id>"
signature = HMAC-SHA256(webhook_secret, base)   // hex

添加 Webhook 对象时,应用会自动生成 webhook_secret :32 个随机十六进制字符。用字段旁的按钮复制,并粘贴到接收端的设置中。您可以换成自己的密钥。没有密钥的 Webhook 在两种模式下都无法保存,因此每个请求都带有签名。

访客永远不会收到密钥:他们的点击由所有者的手机或服务器签名,取决于由谁发送请求。

如何校验(在您这一侧)

  1. 构造同样的字符串 base = timestamp.nonce.action.object_id ——用您收到的字段。
  2. 计算 HMAC-SHA256(webhook_secret, base) ,再与 signature 做常数时间比较(hash_equals / hmac.compare_digest).
  3. 防重放: 若 |now − timestamp| > 300 秒,则拒绝该请求。也可以保存最近的 nonce 值,并拒绝重复的。
  4. 签名一致且请求未过期——执行开启。
Webhook 地址通常是无需认证就能访问的,因此 校验签名是必须的:没有它,任何知道地址的人都能开启。如果更换密钥,请至少使用 16 个字符,例如来自 openssl rand -hex 32。切勿把密钥放进地址本身。

验证代码

每个示例都会检查签名和时间。请将密钥保存在接收端的设置或环境变量中。

PHP

$secret = getenv('ENTRIXY_WEBHOOK_SECRET');
$b = json_decode(file_get_contents('php://input'), true);
$base = $b['timestamp'] . '.' . $b['nonce'] . '.' . $b['action'] . '.' . $b['object_id'];
$ok = hash_equals(hash_hmac('sha256', $base, $secret), (string)($b['signature'] ?? ''))
   && abs(time() - (int)$b['timestamp']) <= 300;
if (!$ok) { http_response_code(401); exit; }
// $b['action'] is "open" or "close"

Python

import hashlib, hmac, os, time

def verify(b: dict) -> bool:
    secret = os.environ["ENTRIXY_WEBHOOK_SECRET"].encode()
    base = f'{b["timestamp"]}.{b["nonce"]}.{b["action"]}.{b["object_id"]}'
    mac = hmac.new(secret, base.encode(), hashlib.sha256).hexdigest()
    return (hmac.compare_digest(mac, str(b.get("signature", "")))
            and abs(time.time() - int(b["timestamp"])) <= 300)

Node.js

const crypto = require('crypto');

function verify(b, secret) {
  const base = `${b.timestamp}.${b.nonce}.${b.action}.${b.object_id}`;
  const mac = crypto.createHmac('sha256', secret).update(base).digest('hex');
  const sig = String(b.signature || '');
  return sig.length === mac.length
    && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig))
    && Math.abs(Date.now() / 1000 - b.timestamp) <= 300;
}

若要在五分钟内也拒绝重放,请记住最近五分钟内的 nonce 直到其 timestamp 超过 300 秒为止,并拒绝重复的请求。

测试向量

用这些取值跑一遍您的校验流程,核对签名:

webhook_secret = webhook_secret_example_key
timestamp      = 1750000000
nonce          = 0011223344556677
action         = open
object_id      = 42

base       = 1750000000.0011223344556677.open.42
signature  = 9d7b58066094fa88acc0afa7aa805fc5f0d008d33cb11b8aa2366102da8e20f1
  = HMAC-SHA256(webhook_secret, base)

响应

若要让应用显示结果,请以 JSON 响应:

{"level":"success","message":"Opened","position":"open","close_in":30}
字段取值
levelsuccess、warning、danger 或 info:应用如何标示结果
message应用在该次按下旁显示的文字
position用于“开启 / 关闭”对象:open、closed 或 unknown
close_in接收端自行关闭前的秒数,最多 3600:时间一到,应用即显示对象已关闭

旧格式的 status 字段(ok 或 error)同样可以识别。两个字段都没有时,由 HTTP 状态码决定:任何 2xx 响应都算成功,其余都算失败。因此一个空的 200 OK 就足够了。

没有 close_in 时,位置在响应后显示两秒,之后变为未知:下一次点击会开启。

发送方式

请求始终是带 JSON 正文的 POST,绝不会是普通的 GET。

因此,只能在家庭网络内访问的接收端,仅在由所有者手机发送请求、且手机处于该网络中时才能工作。可从互联网访问的接收端请使用 https。

无需自己的接收端即可试用

打开 演示道闸:它会为您生成专属测试地址。将其设为对象的地址,并把对象的密钥粘贴到该页面的输入框中 — 每次按下的签名都在您的浏览器中校验,密钥不会发往任何地方。

现成的接收端

curl 发送一个签名正确的测试请求。 Flask 是一个校验签名并拒绝重复请求的接收端。 Home Assistant 可通过该接收端接收命令,也可通过其自带的 Webhook 触发器接收(不校验签名)。

Shelly 和 Tasmota 继电器以及 openHAB 不校验签名。它们通过 Node.js 客户端接入:该客户端自行与 Entrixy 保持连接,并在您的网络内转发命令。