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

Event triggers

Use when event <name> to fire a rule in response to a named application event rather than a device telemetry reading. The event name is a dot-separated identifier (e.g. posthog.signup, billing.paid). Matching is exact and case-sensitive; wildcards are not supported in v1.

Syntax

when event <name> [cooldown <duration>] then <action> [with payload <json-object>]

Complete example

when event posthog.signup cooldown 5m
then send command servo.rotate to device servo-c6
  with payload {"angle": 120, "hold_ms": 2000}

This rule fires whenever a posthog.signup event is received, waits at least 5 minutes before re-firing (cooldown), and sends a servo.rotate command to servo-c6 carrying the JSON payload {"angle": 120, "hold_ms": 2000}.

The with payload block

Any send command action — whether triggered by telemetry or by an event — may carry an optional JSON object payload:

send command <cmd> to <target> with payload {"key": "value"}

Cooldowns for event-triggered rules

Event rules reuse the same cooldown <duration> mechanism as telemetry rules. The cooldown timer starts from the last time the rule fired, regardless of how many events arrive in the interim.

HTTP delivery endpoint coming soon Delivering named events to jettyd via HTTP POST is a follow-up feature (FLU-162). Once available, you will be able to call POST /v1/events to trigger any when event <name> rule from your application server.

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"