请求
每次点击时,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 时间,单位秒 |
nonce | 16 个十六进制字符(8 个随机字节),一次性 |
signature | HMAC-SHA256,见下文 |
“开启 / 关闭”对象根据最后已知的状态交替发送: close 若对象已开启则发送此命令,否则发送 open。服务器模式下由服务器决定,手机模式下由所有者的手机决定。
签名
被签名的是一个 规范化字符串 ,由四个以点号分隔的字段组成,而不是 JSON 正文本身,因此您不必逐字节复刻我们的序列化方式:
base = "<timestamp>.<nonce>.<action>.<object_id>" signature = HMAC-SHA256(webhook_secret, base) // hex
添加 Webhook 对象时,应用会自动生成 webhook_secret :32 个随机十六进制字符。用字段旁的按钮复制,并粘贴到接收端的设置中。您可以换成自己的密钥。没有密钥的 Webhook 在两种模式下都无法保存,因此每个请求都带有签名。
访客永远不会收到密钥:他们的点击由所有者的手机或服务器签名,取决于由谁发送请求。
如何校验(在您这一侧)
- 构造同样的字符串
base = timestamp.nonce.action.object_id——用您收到的字段。 - 计算
HMAC-SHA256(webhook_secret, base),再与signature做常数时间比较(hash_equals/hmac.compare_digest). - 防重放: 若
|now − timestamp| > 300秒,则拒绝该请求。也可以保存最近的nonce值,并拒绝重复的。 - 签名一致且请求未过期——执行开启。
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}
| 字段 | 取值 |
|---|---|
level | success、warning、danger 或 info:应用如何标示结果 |
message | 应用在该次按下旁显示的文字 |
position | 用于“开启 / 关闭”对象:open、closed 或 unknown |
close_in | 接收端自行关闭前的秒数,最多 3600:时间一到,应用即显示对象已关闭 |
旧格式的 status 字段(ok 或 error)同样可以识别。两个字段都没有时,由 HTTP 状态码决定:任何 2xx 响应都算成功,其余都算失败。因此一个空的 200 OK 就足够了。
没有 close_in 时,位置在响应后显示两秒,之后变为未知:下一次点击会开启。
发送方式
请求始终是带 JSON 正文的 POST,绝不会是普通的 GET。
- 从您的手机发送:会跟随一次指向同一主机的重定向,例如从 http 到 https,或到末尾带斜杠的地址。
- 从 Entrixy 服务器发送:不跟随重定向,并拒绝私有网络和内部网络的地址。
因此,只能在家庭网络内访问的接收端,仅在由所有者手机发送请求、且手机处于该网络中时才能工作。可从互联网访问的接收端请使用 https。
无需自己的接收端即可试用
打开 演示道闸:它会为您生成专属测试地址。将其设为对象的地址,并把对象的密钥粘贴到该页面的输入框中 — 每次按下的签名都在您的浏览器中校验,密钥不会发往任何地方。
现成的接收端
curl 发送一个签名正确的测试请求。 Flask 是一个校验签名并拒绝重复请求的接收端。 Home Assistant 可通过该接收端接收命令,也可通过其自带的 Webhook 触发器接收(不校验签名)。
Shelly 和 Tasmota 继电器以及 openHAB 不校验签名。它们通过 Node.js 客户端接入:该客户端自行与 Entrixy 保持连接,并在您的网络内转发命令。