❮  Intégrations

Spécification du webhook

Entrixy envoie à votre adresse une commande d’ouverture ou de fermeture et la signe. Voici le format de la requête et la façon de vérifier la signature, pour accepter la commande dans votre propre service, maison connectée ou carte.

Un webhook est le mode où l’ouverture n’est pas réalisée par notre contrôleur mais par votre propre système : Home Assistant, un relais sur le réseau local, votre propre serveur. Un objet Webhook envoie la même requête signée dans ses deux modes. Depuis votre téléphone : le téléphone du propriétaire l’envoie, et l’adresse et le secret ne quittent jamais le téléphone. Depuis le serveur Entrixy : notre serveur l’envoie, il conserve donc l’adresse et le secret. Récepteurs prêts à l’emploi : curl, Flask, Home Assistant.

La requête

À chaque appui, Entrixy envoie un POST vers votre webhook_url avec un corps JSON :

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

{
  "action":    "open",
  "object_id": 42,
  "timestamp": 1750000000,
  "nonce":     "0011223344556677",
  "signature": "9d7b58066094fa88..."
}
ChampValeur
action"open", pour un objet Ouvrir / fermer aussi "close"
object_idl'identifiant de l'objet dans votre compte
timestampheure unix d'envoi, en secondes
nonce16 caractères hexadécimaux (8 octets aléatoires), à usage unique
signatureHMAC-SHA256, voir plus bas

Un objet Ouvrir / fermer alterne selon sa dernière position connue : close s’il est ouvert, sinon open. En mode serveur, c’est le serveur qui décide ; en mode téléphone, le téléphone du propriétaire.

La signature

Ce qui est signé, c'est une chaîne canonique de quatre champs séparés par des points, et non le corps JSON lui-même : vous n'avez donc pas à reproduire notre sérialisation octet par octet :

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

L’application crée webhook_secret d’elle-même lorsque vous ajoutez un objet Webhook : 32 caractères hexadécimaux aléatoires. Copiez-le avec le bouton à côté du champ dans les réglages de votre récepteur. Vous pouvez le remplacer par le vôtre. Un webhook sans secret ne peut être enregistré dans aucun des deux modes, chaque requête est donc signée.

Les invités ne reçoivent jamais le secret : leurs appuis sont signés par le téléphone du propriétaire ou par le serveur, selon celui qui envoie la requête.

Comment la vérifier (de votre côté)

  1. Construisez la même chaîne base = timestamp.nonce.action.object_id à partir des champs reçus.
  2. Calculez HMAC-SHA256(webhook_secret, base) et comparez-le avec signature avec une comparaison à temps constant (hash_equals / hmac.compare_digest).
  3. Anti-rejeu : rejetez la requête si |now − timestamp| > 300 secondes. Facultativement, conservez les derniers nonce valeurs et rejetez les répétitions.
  4. La signature correspond et la requête est fraîche — exécutez l'ouverture.
Une adresse de webhook est généralement ouverte sans authentification, c'est pourquoi la vérification de la signature est obligatoire : sans elle, quiconque connaît l’adresse peut ouvrir. Si vous remplacez le secret, utilisez au moins 16 caractères, par exemple issus de openssl rand -hex 32. Ne mettez jamais le secret dans l’adresse elle-même.

Code de vérification

Chaque exemple vérifie la signature et l’heure. Conservez le secret dans les réglages de votre récepteur ou dans une variable d’environnement.

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;
}

Pour refuser aussi une répétition dans les cinq minutes, mémorisez les valeurs nonce jusqu’à ce que leur timestamp date de plus de 300 secondes, et refusez les répétitions.

Vecteur de test

Faites tourner votre vérification sur ces valeurs et contrôlez la signature :

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 réponse

Pour que l’application affiche un résultat, répondez en JSON :

{"level":"success","message":"Opened","position":"open","close_in":30}
ChampValeur
levelsuccess, warning, danger ou info : la façon dont l’application signale le résultat
messagele texte que l’application affiche à côté de l’appui
positionpour un objet Ouvrir / fermer : open, closed ou unknown
close_insecondes avant que le récepteur ne ferme de lui-même, 3600 au plus : une fois écoulées, l’application affiche l’objet fermé

L’ancienne réponse avec le champ status (ok ou error) est également comprise. Sans l’un ni l’autre, c’est le code HTTP qui décide : toute réponse 2xx est un succès, tout le reste un échec. Un 200 OK vide suffit donc.

Sans close_in, la position est affichée deux secondes après la réponse, puis devient inconnue : le prochain appui ouvre.

Envoi

La requête est toujours un POST avec un corps JSON, jamais un simple GET.

Un récepteur accessible uniquement sur votre réseau domestique ne fonctionne donc que lorsque c’est le téléphone du propriétaire qui envoie la requête, et seulement tant que le téléphone est sur ce réseau. Pour un récepteur accessible depuis internet, utilisez https.

Essayer sans votre propre récepteur

Ouvrez la barrière de démonstration : elle vous donne une adresse de test personnelle. Indiquez-la comme adresse de l’objet et collez le secret de l’objet dans le champ de cette page — la signature de chaque appui est vérifiée dans votre navigateur, et le secret n’est envoyé nulle part.

Récepteurs tout prêts

curl envoie une requête de test avec une signature correcte. Flask est un récepteur qui vérifie la signature et refuse les répétitions. Home Assistant reçoit la commande par ce récepteur, ou par son propre déclencheur webhook sans vérification de signature.

Les relais Shelly et Tasmota ainsi qu’openHAB ne vérifient pas la signature. Ils se connectent via le client Node.js : il maintient lui-même la connexion avec Entrixy et transmet la commande à l’intérieur de votre réseau.