Спецификация вебхука
Entrixy отправляет на ваш адрес команду открыть или закрыть и подписывает её. Здесь формат запроса и способ проверить подпись, чтобы принимать команду в своём сервисе, умном доме или на плате.
Запрос
При нажатии Entrixy отправляет POST на ваш webhook_url с JSON-телом:
POST <ваш 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 hex-символов (8 случайных байт), одноразовый |
signature | HMAC-SHA256, см. ниже |
Объект «Открыть / закрыть» чередует команды по последнему известному положению: close — если он открыт, иначе open. В режиме «С сервера» решает сервер, в режиме «С телефона» — телефон владельца.
Подпись
Подписывается каноническая строка из четырёх полей через точку, а не само JSON-тело — так вам не придётся побайтно повторять нашу сериализацию:
base = "<timestamp>.<nonce>.<action>.<object_id>" signature = HMAC-SHA256(webhook_secret, base) // hex
Приложение само создаёт webhook_secret при добавлении объекта «Вебхук»: 32 случайных шестнадцатеричных символа. Скопируйте его кнопкой рядом с полем в настройки своего приёмника. Секрет можно заменить своим. Вебхук без секрета не сохраняется ни в одном из режимов, поэтому каждый запрос подписан.
Гости секрет не получают никогда: их нажатия подписывает телефон владельца или сервер — тот, кто отправляет запрос.
Как проверить (получатель)
- Соберите ту же строку
base = timestamp.nonce.action.object_idиз принятых полей. - Посчитайте
HMAC-SHA256(webhook_secret, base)и сверьте сsignatureчерез constant-time сравнение (hash_equals/hmac.compare_digest). - Анти-replay: отклоните, если
|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 положение показывается две секунды после ответа, потом становится неизвестным: следующее нажатие открывает.
Отправка
Запрос всегда приходит методом POST с телом в формате JSON, никогда простым GET.
- С вашего телефона: выполняется одно перенаправление на тот же хост, например с http на https или на адрес с косой чертой в конце.
- С сервера Entrixy: перенаправления не выполняются, а адреса частных и внутренних сетей отклоняются.
Поэтому приёмник, доступный только в домашней сети, работает, лишь когда запрос отправляет телефон владельца, и только пока телефон подключён к этой сети. Для приёмника, доступного из интернета, используйте https.
Попробовать без своего приёмника
Откройте демо-шлагбаум: он выдаст вам личный проверочный адрес. Укажите его адресом объекта и вставьте секрет объекта в поле на той странице — подпись каждого нажатия проверяется прямо в браузере, а секрет никуда не отправляется.
Готовые приёмники
curl отправляет тестовый запрос с правильной подписью. Flask — приёмник, который проверяет подпись и отклоняет повторы. Home Assistant принимает команду через этот приёмник или через собственный триггер вебхука без проверки подписи.
Реле Shelly и Tasmota, а также openHAB подпись не проверяют. Они подключаются через клиент на Node.js: он сам держит соединение с Entrixy и передаёт команду дальше внутри вашей сети.