Automations & Webhooks

Use JettyScript rules so sensors trigger AI agents — not the other way around.

Overview

jettyd includes a built-in rules engine called JettyScript. Rules evaluate every incoming telemetry value and fire actions when conditions are met — sending webhooks, alerts, device commands, or pushing data to an AI agent.

The standard pattern is: device publishes telemetry → rule fires → webhook hits your server → your server calls the Claude API. This inverts the usual polling loop: the sensor wakes the agent instead of the agent polling for changes.

JettyScript basics

Rules follow a when … then … syntax:

when <metric> <operator> <value> [cooldown <duration>] then <action>

Condition operators

Cooldown

Add cooldown 10m (or 30s, 2h) to suppress repeated firings. Without a cooldown, the rule fires on every matching telemetry message.

Actions

Creating a webhook destination

Webhooks are created in the dashboard under Webhooks or via the API. Each webhook has a signing secret used to verify delivery authenticity.

Via the dashboard

  1. Open the dashboard → WebhooksCreate Webhook.
  2. Enter a name, your HTTPS endpoint URL, and the event patterns to subscribe to.
  3. Copy the displayed secret — it is shown only once.

Via the API

curl -X POST https://api.jettyd.com/v1/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Claude trigger",
    "url": "https://your-server.example.com/jettyd-hook",
    "events": ["alert.*", "telemetry.*"]
  }'

The response contains a one-time secret field. Store it; you'll use it to verify incoming requests.

Filtering events

Use these patterns in the events array:

You can also filter by device ID, device group, or device type using the filter_devices, filter_groups, and filter_device_types fields.

Verifying HMAC signatures

Every delivery includes an X-Jettyd-Signature header with a hex-encoded HMAC-SHA256 of the raw request body, keyed with your webhook secret.

import hmac, hashlib

def verify_jettyd_signature(payload: bytes, header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)
Always verify signatures Reject any request where the signature does not match. This prevents replay attacks and spoofed deliveries.

Worked example: temperature threshold → Claude API

This example fires a JettyScript rule when a temperature sensor exceeds 40 °C, posts to your server, and your server calls the Claude API to generate an alert summary and decide on an action.

Step 1 — Create the rule

when temperature > 40 cooldown 10m then send alert "High temperature"

In the dashboard go to Rules → Create Rule, paste the rule text, and optionally scope it to a specific device.

Step 2 — Create the webhook

curl -X POST https://api.jettyd.com/v1/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Temperature alert to Claude",
    "url": "https://your-server.example.com/hook",
    "events": ["alert.*"]
  }'

Save the returned secret.

Step 3 — Receive the webhook

jettyd sends a POST with a JSON body like:

{
  "event_id": "evt_01jx…",
  "event_type": "alert.warning",
  "tenant_id": "…",
  "device": {
    "id": "…",
    "key": "dk_abc123",
    "name": "Greenhouse sensor 1",
    "device_type": "env-sensor"
  },
  "data": {
    "level": "warning",
    "message": "High temperature",
    "metric": "temperature",
    "value": 42.1
  },
  "occurred_at": "2026-06-12T10:34:22Z"
}

Step 4 — Call the Claude API

Your server verifies the signature, then calls the Anthropic API to let Claude interpret the alert and decide on a next action (e.g. send a command, page someone, or log the event).

import anthropic, json

def handle_jettyd_webhook(payload: dict) -> str:
    client = anthropic.Anthropic()

    device_name = payload["device"]["name"]
    alert_msg   = payload["data"]["message"]
    metric      = payload["data"].get("metric", "unknown")
    value       = payload["data"].get("value")

    message = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=256,
        system=(
            "You are an IoT monitoring assistant. "
            "When a sensor fires an alert, provide a one-sentence diagnosis "
            "and the single most important action to take."
        ),
        messages=[{
            "role": "user",
            "content": (
                f"Device '{device_name}' fired alert: {alert_msg}. "
                f"Current {metric} reading: {value}. "
                "What should I do?"
            )
        }]
    )

    return message.content[0].text
Use prompt caching for repeated context If you pass device configuration or historical baselines in every request, mark that content with cache_control: {"type": "ephemeral"} to reduce latency and cost.

Step 5 — Act on Claude's response

Parse Claude's response and call the jettyd command API to act on the device, or send the summary to a Slack channel or incident tracker.

import requests

def send_device_command(device_id: str, command: str, api_key: str):
    requests.post(
        f"https://api.jettyd.com/v1/devices/{device_id}/commands",
        headers={"Authorization": f"Bearer {api_key}"},
        json={"command_type": command, "payload": {}}
    )

Delivery retries and logs

jettyd retries failed deliveries up to 3 times with exponential back-off. After 3 attempts the delivery is marked dead_letter. You can inspect all delivery attempts in the dashboard under Webhooks → Log, or via the API:

curl https://api.jettyd.com/v1/webhooks/{id}/deliveries \
  -H "Authorization: Bearer $API_KEY"

Viewing rule firings per device

In the device detail page open the Rules tab to see which rules apply and a time-ordered log of recent firings, including the action taken and result. You can also query the API:

curl https://api.jettyd.com/v1/devices/{id}/rules/logs \
  -H "Authorization: Bearer $API_KEY"