❮  Integraciones

Especificación del webhook

Entrixy envía a su dirección una orden de abrir o cerrar y la firma. Aquí tiene el formato de la petición y cómo verificar la firma, para aceptar la orden en su propio servicio, hogar inteligente o placa.

Un webhook es el modo en el que la apertura no la realiza nuestro controlador, sino su propio sistema: Home Assistant, un relé en la red local, su propio servidor. Un objeto Webhook envía la misma petición firmada en sus dos modos. Desde su teléfono: la envía el teléfono del propietario, y la dirección y el secreto nunca salen del teléfono. Desde el servidor de Entrixy: la envía nuestro servidor, que por eso guarda la dirección y el secreto. Receptores listos: curl, Flask, Home Assistant.

La petición

Al pulsar, Entrixy envía un POST a tu webhook_url con un cuerpo JSON:

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

{
  "action":    "open",
  "object_id": 42,
  "timestamp": 1750000000,
  "nonce":     "0011223344556677",
  "signature": "9d7b58066094fa88..."
}
CampoValor
action"open", para un objeto Abrir / cerrar también "close"
object_idel id del objeto en tu cuenta
timestamphora unix de envío, en segundos
nonce16 caracteres hex (8 bytes aleatorios), de un solo uso
signatureHMAC-SHA256, ver más abajo

Un objeto Abrir / cerrar alterna según su última posición conocida: close cuando está abierto, si no open. En el modo servidor decide el servidor; en el modo teléfono, el teléfono del propietario.

La firma

Lo que se firma es una cadena canónica de cuatro campos separados por puntos, no el cuerpo JSON en sí, así que no tienes que reproducir nuestra serialización byte a byte:

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

La aplicación crea webhook_secret por sí misma al añadir un objeto Webhook: 32 caracteres hexadecimales aleatorios. Cópielo con el botón junto al campo en la configuración de su receptor. Puede sustituirlo por uno propio. Un webhook sin secreto no se puede guardar en ninguno de los dos modos, así que toda petición va firmada.

Los invitados nunca reciben el secreto: sus pulsaciones las firma el teléfono del propietario o el servidor, según quién envíe la petición.

Cómo verificarla (en tu lado)

  1. Construye la misma cadena base = timestamp.nonce.action.object_id a partir de los campos recibidos.
  2. Calcula HMAC-SHA256(webhook_secret, base) y compáralo con signature con una comparación de tiempo constante (hash_equals / hmac.compare_digest).
  3. Antirrepetición: rechaza la petición si |now − timestamp| > 300 segundos. Opcionalmente, guarda los últimos nonce valores y rechaza las repeticiones.
  4. Si la firma cuadra y la petición es reciente, ejecuta la apertura.
Una dirección de webhook suele estar abierta sin autenticación, así que verificar la firma es obligatorio: sin ella, cualquiera que conozca la dirección puede abrir. Si sustituye el secreto, use al menos 16 caracteres, por ejemplo de openssl rand -hex 32. Nunca ponga el secreto en la propia dirección.

Código de verificación

Cada ejemplo comprueba la firma y la hora. Guarde el secreto en la configuración de su receptor o en una variable de entorno.

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 rechazar también una repetición dentro de los cinco minutos, recuerde los valores nonce hasta que su timestamp tenga más de 300 segundos de antigüedad, y rechace las repeticiones.

Vector de prueba

Pasa tu verificación por estos valores y comprueba la firma:

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)

La respuesta

Para que la app muestre un resultado, responda con JSON:

{"level":"success","message":"Opened","position":"open","close_in":30}
CampoValor
levelsuccess, warning, danger o info: cómo marca la app el resultado
messageel texto que la app muestra junto a la pulsación
positionpara un objeto Abrir / cerrar: open, closed o unknown
close_insegundos hasta que el receptor cierre por sí mismo, como máximo 3600: al agotarse, la app muestra el objeto cerrado

También se entiende la respuesta anterior con el campo status (ok o error). Sin ninguno de los dos campos decide el código HTTP: cualquier respuesta 2xx es un éxito y todo lo demás un fallo. Así que basta con un 200 OK vacío.

Sin close_in, la posición se muestra dos segundos después de la respuesta y luego pasa a ser desconocida: la siguiente pulsación abre.

Envío

La petición es siempre un POST con cuerpo JSON, nunca un GET simple.

Por eso, un receptor accesible solo en su red doméstica funciona únicamente cuando la petición la envía el teléfono del propietario, y solo mientras el teléfono esté en esa red. Para un receptor accesible desde internet, use https.

Pruébelo sin receptor propio

Abra la barrera de demostración: le da una dirección de prueba personal. Póngala como dirección del objeto y pegue el secreto del objeto en el campo de esa página: la firma de cada pulsación se comprueba en su navegador y el secreto no se envía a ningún sitio.

Receptores ya hechos

curl envía una petición de prueba con una firma correcta. Flask es un receptor que comprueba la firma y rechaza las repeticiones. Home Assistant recibe la orden a través de ese receptor o de su propio disparador de webhook sin comprobar la firma.

Los relés Shelly y Tasmota y openHAB no comprueban la firma. Se conectan a través de el cliente en Node.js: mantiene su propia conexión con Entrixy y transmite la orden dentro de su red.