Especificação do webhook
A Entrixy envia ao seu endereço um comando de abrir ou fechar e o assina. Aqui está o formato do pedido e como verificar a assinatura, para aceitar o comando no seu próprio serviço, casa inteligente ou placa.
O pedido
A cada toque, a Entrixy envia um POST para o seu webhook_url com um corpo JSON:
POST <your webhook_url>
Content-Type: application/json
{
"action": "open",
"object_id": 42,
"timestamp": 1750000000,
"nonce": "0011223344556677",
"signature": "9d7b58066094fa88..."
}
| Campo | Valor |
|---|---|
action | "open", para um objeto Abrir / fechar também "close" |
object_id | o id do objeto na sua conta |
timestamp | hora unix de envio, em segundos |
nonce | 16 carateres hex (8 bytes aleatórios), de uso único |
signature | HMAC-SHA256, ver abaixo |
Um objeto Abrir / fechar alterna conforme a sua última posição conhecida: close quando está aberto, caso contrário open. No modo servidor quem decide é o servidor; no modo telefone, o telefone do proprietário.
A assinatura
O que se assina é uma cadeia canónica de quatro campos separados por pontos, e não o corpo JSON em si, por isso não tem de reproduzir a nossa serialização byte a byte:
base = "<timestamp>.<nonce>.<action>.<object_id>" signature = HMAC-SHA256(webhook_secret, base) // hex
O app cria webhook_secret sozinho quando você adiciona um objeto Webhook: 32 caracteres hexadecimais aleatórios. Copie-o com o botão ao lado do campo para as configurações do seu receptor. Você pode substituí-lo por um seu. Um webhook sem segredo não pode ser salvo em nenhum dos dois modos, por isso todo pedido é assinado.
Os convidados nunca recebem o segredo: os seus toques são assinados pelo telefone do proprietário ou pelo servidor, conforme quem envia o pedido.
Como verificá-la (do seu lado)
- Construa a mesma cadeia
base = timestamp.nonce.action.object_ida partir dos campos recebidos. - Calcule
HMAC-SHA256(webhook_secret, base)e compare-o comsignaturecom uma comparação de tempo constante (hash_equals/hmac.compare_digest). - Antirrepetição: rejeite o pedido se
|now − timestamp| > 300segundos. Opcionalmente, guarde os últimosnoncevalores e rejeite repetições. - Se a assinatura bate certo e o pedido é recente, execute a abertura.
openssl rand -hex 32. Nunca coloque o segredo no próprio endereço.Código de verificação
Cada exemplo verifica a assinatura e a hora. Guarde o segredo nas configurações do seu receptor ou numa variável de ambiente.
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;
}
Para recusar também uma repetição dentro dos cinco minutos, guarde os valores de nonce até que o respetivo timestamp tenha mais de 300 segundos, e rejeite as repetições.
Vetor de teste
Passe a sua verificação por estes valores e confira a assinatura:
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)
A resposta
Para que a app mostre um resultado, responda com JSON:
{"level":"success","message":"Opened","position":"open","close_in":30}
| Campo | Valor |
|---|---|
level | success, warning, danger ou info: como a app marca o resultado |
message | o texto que a app mostra junto ao toque |
position | para um objeto Abrir / fechar: open, closed ou unknown |
close_in | segundos até o receptor fechar sozinho, no máximo 3600: quando acabam, a app mostra o objeto fechado |
A resposta antiga com o campo status (ok ou error) também é entendida. Sem nenhum dos dois campos, decide o código HTTP: qualquer resposta 2xx é sucesso, todo o resto é falha. Por isso basta um 200 OK vazio.
Sem close_in, a posição é mostrada por dois segundos após a resposta e depois fica desconhecida: o próximo toque abre.
Envio
O pedido é sempre um POST com corpo JSON, nunca um GET simples.
- A partir do seu telefone: é seguido um redirecionamento para o mesmo host, por exemplo de http para https ou para o endereço com barra no final.
- A partir do servidor Entrixy: os redirecionamentos não são seguidos e os endereços de redes privadas e internas são recusados.
Por isso, um receptor acessível apenas na sua rede doméstica só funciona quando é o telefone do proprietário que envia o pedido, e só enquanto o telefone está nessa rede. Para um receptor acessível pela internet, use https.
Experimente sem um receptor próprio
Abra a cancela de demonstração: ela dá-lhe um endereço de teste pessoal. Defina-o como endereço do objeto e cole o segredo do objeto no campo dessa página — a assinatura de cada toque é verificada no seu navegador e o segredo não é enviado para lado nenhum.
Recetores já prontos
curl envia um pedido de teste com uma assinatura correta. Flask é um receptor que verifica a assinatura e recusa repetições. Home Assistant recebe o comando por esse receptor ou pelo seu próprio gatilho de webhook sem verificação da assinatura.
Os relés Shelly e Tasmota e o openHAB não verificam a assinatura. Ligam-se através de o cliente em Node.js: ele mantém a sua própria ligação ao Entrixy e passa o comando dentro da sua rede.