Skip to content

React to Device Events

Webhooks let you run code when things happen in your home. When a device changes state, Homecast sends an HTTP POST to your URL with the event details.

Device → HomeKit → Relay → Server → Your webhook endpoint

Create a webhook

Use the GraphQL API to create a webhook:

graphql
mutation {
  createWebhook(
    name: "My Automation"
    url: "https://your-server.com/webhook"
    eventTypes: ["state.changed"]
    homeIds: ["home-uuid"]
  ) {
    success
    webhook {
      id
      name
      rawSecret
    }
  }
}

Save the secret

The rawSecret is only returned on creation. Store it immediately — you'll need it to verify webhook signatures.

Filter events

Control which events trigger your webhook:

FilterExampleDescription
eventTypes["state.changed"]Which event types to receive. Use ["*"] for all.
homeIds["home-uuid"]Only events from these homes
roomIds["room-uuid"]Only events from these rooms
accessoryIds["acc-uuid"]Only events from these accessories
collectionIds["coll-uuid"]Only events from accessories in these collections

Payload format

When an event fires, Homecast POSTs a JSON payload to your URL:

json
{
  "id": "evt-uuid",
  "type": "state.changed",
  "timestamp": "2026-02-16T08:30:00Z",
  "webhook_id": "webhook-uuid",
  "data": {
    "accessoryId": "acc-uuid",
    "accessoryName": "Motion Sensor",
    "characteristicType": "motion_detected",
    "value": true,
    "homeId": "home-uuid",
    "roomId": "room-uuid"
  },
  "metadata": {
    "webhook_version": "1.0",
    "webhook_name": "My Automation"
  }
}

Headers

Every webhook delivery includes these headers:

HeaderDescription
Content-Typeapplication/json
X-Homecast-SignatureHMAC signature for verification
X-Homecast-TimestampUnix timestamp of the delivery
X-Homecast-EventEvent type (e.g., state.changed)
X-Homecast-DeliveryUnique delivery ID
User-AgentHomecast-Webhook/1.0

Verify signatures

Always verify webhook signatures to ensure the request came from Homecast. The signature uses HMAC-SHA256.

Algorithm:

  1. Read the X-Homecast-Signature header: t={timestamp},v1={signature}
  2. Build the signed message: {timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256 of the message using your webhook secret
  4. Compare with the v1 value from the header
python
import hmac
import hashlib

def verify_webhook(payload: bytes, signature_header: str, secret: str) -> bool:
    # Parse "t=123456,v1=abc..."
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp = parts["t"]
    expected_sig = parts["v1"]

    # Build signed message
    message = f"{timestamp}.{payload.decode()}"
    computed = hmac.new(
        secret.encode(), message.encode(), hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(computed, expected_sig)
javascript
const crypto = require("crypto");

function verifyWebhook(payload, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map(p => p.split("=", 2))
  );
  const message = `${parts.t}.${payload}`;
  const computed = crypto
    .createHmac("sha256", secret)
    .update(message)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(parts.v1)
  );
}
go
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "strings"
)

func verifyWebhook(payload []byte, signatureHeader, secret string) bool {
    parts := make(map[string]string)
    for _, p := range strings.Split(signatureHeader, ",") {
        kv := strings.SplitN(p, "=", 2)
        parts[kv[0]] = kv[1]
    }
    message := fmt.Sprintf("%s.%s", parts["t"], string(payload))
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(parts["v1"]))
}

Timestamp tolerance

Reject deliveries where the timestamp is more than 5 minutes old to prevent replay attacks.

Retry and reliability

If your endpoint returns a non-2xx status code or is unreachable, Homecast retries with exponential backoff:

AttemptDelay
1Immediate
21 second
32 seconds
44 seconds
58 seconds
616 seconds

After all retries are exhausted, the delivery is marked as dead_letter.

Circuit breaker: If a webhook accumulates 5 consecutive failures, Homecast pauses it automatically. Fix the issue, then resume the webhook to reset the counter.

Manage webhooks

Use GraphQL mutations to manage your webhooks:

MutationDescription
createWebhook(...)Create a new webhook
updateWebhook(id, ...)Update URL, filters, or name
pauseWebhook(id)Pause deliveries
resumeWebhook(id)Resume a paused webhook
rotateWebhookSecret(id)Generate a new signing secret
testWebhook(id)Send a test delivery
deleteWebhook(id)Permanently delete
webhookDeliveries(webhookId)View delivery history

Example: motion-triggered notification

A Flask endpoint that receives motion events and sends a push notification:

python
from flask import Flask, request, jsonify
import hmac, hashlib, requests

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"
PUSHOVER_TOKEN = "your_pushover_token"
PUSHOVER_USER = "your_pushover_user"

@app.post("/webhook")
def handle_webhook():
    # Verify signature
    sig = request.headers.get("X-Homecast-Signature", "")
    if not verify_signature(request.data, sig, WEBHOOK_SECRET):
        return jsonify({"error": "invalid signature"}), 401

    event = request.json
    data = event["data"]

    if data.get("characteristicType") == "motion_detected" and data.get("value"):
        requests.post("https://api.pushover.net/1/messages.json", data={
            "token": PUSHOVER_TOKEN,
            "user": PUSHOVER_USER,
            "message": f"Motion detected: {data['accessoryName']}"
        })

    return jsonify({"ok": True})

def verify_signature(payload, signature_header, secret):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    message = f"{parts['t']}.{payload.decode()}"
    computed = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, parts["v1"])

For the full webhook configuration reference, see Webhooks reference.