❮  Integrações

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.

Um webhook é o modo em que a abertura não é feita pelo nosso controlador, mas pelo seu próprio sistema: Home Assistant, um relé na rede local, o seu próprio servidor. Um objeto Webhook envia o mesmo pedido assinado nos seus dois modos. A partir do seu telefone: quem envia é o telefone do proprietário, e o endereço e o segredo nunca saem do telefone. A partir do servidor Entrixy: quem envia é o nosso servidor, que por isso guarda o endereço e o segredo. Receptores prontos: curl, Flask, Home Assistant.

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..."
}
CampoValor
action"open", para um objeto Abrir / fechar também "close"
object_ido id do objeto na sua conta
timestamphora unix de envio, em segundos
nonce16 carateres hex (8 bytes aleatórios), de uso único
signatureHMAC-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)

  1. Construa a mesma cadeia base = timestamp.nonce.action.object_id a partir dos campos recebidos.
  2. Calcule HMAC-SHA256(webhook_secret, base) e compare-o com signature com uma comparação de tempo constante (hash_equals / hmac.compare_digest).
  3. Antirrepetição: rejeite o pedido se |now − timestamp| > 300 segundos. Opcionalmente, guarde os últimos nonce valores e rejeite repetições.
  4. Se a assinatura bate certo e o pedido é recente, execute a abertura.
Um endereço de webhook costuma estar aberto sem autenticação, por isso verificar a assinatura é obrigatório: sem ela, qualquer pessoa que conheça o endereço pode abrir. Se substituir o segredo, use pelo menos 16 caracteres, por exemplo de 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}
CampoValor
levelsuccess, warning, danger ou info: como a app marca o resultado
messageo texto que a app mostra junto ao toque
positionpara um objeto Abrir / fechar: open, closed ou unknown
close_insegundos 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.

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.