Home Assistant has no native “custom webhook” notification service the way DSM or Proxmox do — you build the payload yourself inside an automation, using the rest_command or notify.rest integration. The upside: you control every field. The downside: there is no official external contract Tempo can conform to. This page documents the contract Tempo expects, and gives you a drop-in Jinja template to produce it.
This score ships with 13 severity rules that demonstrate the full range of Tempo’s matching engine: multi-key matches (entity_id + state), glob patterns, presentation templates that rewrite the title with values pulled from the payload.
No adapter on the Tempo side is required.
Install
- Download
home-assistant.tempo-scorefrom the button above (or keep the bundled copy Tempo seeded on first launch). - Double-click. Tempo opens a review sheet — click Install.
- In Tempo Settings → Ingestion, add a token named
home-assistantbound tocom.home-assistant. Copy the token. - Note your Tempo endpoint:
http://<your-mac-hostname>:7776/events. - Configure Home Assistant (see below).
Home Assistant side — the rest_command
Add this to your configuration.yaml:
rest_command:
tempo_event:
url: "http://your-mac.local:7776/events"
method: POST
headers:
Content-Type: "application/json"
X-Tempo-Token: "YOUR_TOKEN_HERE"
payload: >
{{ payload }}
content_type: "application/json"
Reload REST commands (Developer Tools → YAML → REST Commands) or restart HA.
The payload contract
Tempo expects this shape. Fields marked required are the minimum; everything else is optional metadata that your severity rules and actions can reference with ${metadata.xxx}.
{
"title": "string — human-readable title (required; overridden by score rules)",
"providerIdentifier": "com.home-assistant",
"eventType": "alert",
"metadata": {
"entity_id": "binary_sensor.kitchen_smoke",
"state": "on",
"friendly_name": "Kitchen smoke detector",
"area": "Kitchen",
"device_class": "smoke",
"automation": "optional — set this instead of entity_id for automation triggers"
}
}
Reserved Tempo fields (title, providerIdentifier, eventType) are at the top level. Everything Home Assistant specific lives in metadata.
Jinja template — one automation, all entities
Drop this automation into Settings → Automations & Scenes. It fires on any state change for any entity in the domains you care about (smoke, leak, door, window, motion, battery, climate), builds the Tempo payload with interpolated values, and POSTs it.
alias: "Tempo — forward state changes"
mode: queued
max: 25
trigger:
- platform: state
entity_id:
- binary_sensor.kitchen_smoke
- binary_sensor.garage_leak
- binary_sensor.front_door
- binary_sensor.living_room_motion
- sensor.master_bedroom_battery
- climate.living_room
action:
- service: rest_command.tempo_event
data:
payload: >-
{
"title": "{{ state_attr(trigger.entity_id, 'friendly_name') or trigger.entity_id }}",
"providerIdentifier": "com.home-assistant",
"eventType": "alert",
"metadata": {
"entity_id": "{{ trigger.entity_id }}",
"state": "{{ trigger.to_state.state }}",
"friendly_name": "{{ state_attr(trigger.entity_id, 'friendly_name') or trigger.entity_id }}",
"area": "{{ area_name(trigger.entity_id) or 'Home' }}",
"device_class": "{{ state_attr(trigger.entity_id, 'device_class') or '' }}"
}
}
For automation events (e.g. a scene firing), use a separate automation that sets metadata.automation instead of metadata.entity_id:
alias: "Tempo — forward automation fires"
trigger:
- platform: event
event_type: automation_triggered
action:
- service: rest_command.tempo_event
data:
payload: >-
{
"title": "Automation: {{ trigger.event.data.name }}",
"providerIdentifier": "com.home-assistant",
"eventType": "alert",
"metadata": {
"automation": "{{ trigger.event.data.name }}"
}
}
How the score classifies events
The bundled score runs device_class-first: matches on the stable device_class attribute that Home Assistant assigns to entities (e.g. smoke, moisture, motion, tamper) take priority over entity_id substring patterns. This is more robust because raw integration names like binary_sensor.0x00158d0001abc_ias_zone (Zigbee2MQTT) carry no semantic info — device_class is the signal that survives renames.
Rules are evaluated in order, first match wins. Highlights:
| Severity | What triggers it |
|---|---|
critical | device_class: smoke / carbon_monoxide / gas / moisture / safety (state on); alarm_control_panel.* state=triggered; legacy entity_id glob (*_smoke*, *_leak*, *_water*) for users who haven’t set device_class |
warning | device_class: tamper / problem (state on); device_class: battery (low); alarm_control_panel.* state=pending; event_type: homeassistant_stop |
info | Door/window/motion sensors; alarm panel armed/disarmed/arming transitions; locks (locked/unlocked); presence (person.* / device_tracker.* home/not_home); homeassistant_start; device_class: update; climate domain catch-all; automation: * |
ok | Smoke cleared; moisture cleared (state: off for those classes) |
For the full list (~30 rules with title/subtitle templates), open the score file in Tempo’s Score Editor or read ~/Library/Application Support/Tempo/Scores/com.home-assistant.json.
Why device_class beats entity_id matching
Two of the most common HA installs (Zigbee2MQTT, ESPHome auto-discovery) name entities by hardware ID, not semantics. A smoke detector might be binary_sensor.0x00158d0001abc_ias_zone rather than binary_sensor.kitchen_smoke. The legacy entity_id glob rules catch the second pattern but miss the first. The device_class: smoke match catches both, because Home Assistant assigns device_class based on what the sensor reports, not how you named it.
The legacy entity_id rules are kept as a fallback — for users who haven’t set device_class on a custom integration, named their entities semantically, or are using older HA Core versions where some integrations didn’t expose device_class.
Match semantics
- Multi-key — all keys in a rule’s
matchdict must match (AND). The smoke-detector rule above fires only forbinary_sensor.*_smoke*entities andstate: "on". The complementary rule (state:off) renders a “cleared” event withokseverity — one sensor, two lines of timeline, both self-explanatory. - Globs —
*matches any run of characters,?matches a single character. Sobinary_sensor.*_smoke*matchesbinary_sensor.kitchen_smoke,binary_sensor.garage_smoke_detector, etc. - First match wins — rules are evaluated top-to-bottom. Put specific rules above general ones. The bundled HA score ends with a catch-all
automation: "*"rule so nothing falls through to the severity default. - Presentation templates —
titleandsubtitleare interpolated at render time. If the referenced key is missing from metadata, the raw template is shown — that’s usually enough to spot the typo.
Actions provided (5 total)
| Group | Actions |
|---|---|
| HA UI | Open dashboard · Open entity history · Open automations page |
| Clipboard | Copy entity ID |
| Docs | Home Assistant docs |
Customizing
- Different HA URL — edit the
openURLin each action (defaulthttp://homeassistant.local:8123). If you use HTTPS with a custom domain, change that first. - More entity types — add rules to the score via the in-app Score editor (Timeline tab). Click + Add rule, add your match conditions, pick a severity, and optionally write a title template. The Try panel on the right lets you drop a recent event against the rule and preview the badge + resolved title before saving.
- Hide noisy entities — either drop them from the automation’s
entity_idlist (don’t send at all), or write a rule that matches them and tag it withseverity: info+ no title override so they’re visible but quiet.
Verifying
Trigger any of the entities you listed in the automation. Tempo’s live feed should show a new alert within a second.
If not, see Troubleshooting below.
Troubleshooting
If events don’t land in Tempo, run these five checks in order.
1. Is Tempo reachable from Home Assistant? — SSH to the HA host (or use the SSH add-on terminal) and run:
curl -v http://your-mac.local:7776/health
A 200 OK means reachability is fine. A timeout or “No route to host” is a network problem (firewall on the Mac, WiFi isolation, HA running in a VLAN that can’t reach the Mac).
2. Does the token work and does Tempo accept your payload shape? — from the same HA host, send a synthetic event that mimics the automation’s payload:
curl -X POST http://your-mac.local:7776/events \
-H 'Content-Type: application/json' \
-H 'X-Tempo-Token: YOUR_TOKEN_HERE' \
-d '{"title":"smoke probe","providerIdentifier":"com.home-assistant","eventType":"alert","metadata":{"entity_id":"binary_sensor.kitchen_smoke","state":"on","friendly_name":"Kitchen smoke detector","area":"Kitchen","device_class":"smoke"}}'
A 200 or 202 with “Smoke detected in Kitchen” appearing in Tempo’s feed means ingestion + the score’s presentation rule both work. A 401 means the token is wrong or not bound to com.home-assistant; a 422 means the JSON is malformed.
3. Are the packets reaching the Mac? — open Terminal on the Mac and watch inbound traffic:
sudo tcpdump -i any -A 'tcp port 7776 and src host your-ha-host.local'
Trigger a state change in HA. You should see the JSON body in the output. If nothing appears, HA’s rest_command is failing before the request leaves — check HA logs (Settings → System → Logs) for errors on rest_command.tempo_event.
4. What is Tempo doing right now? — stream Tempo’s live logs:
log stream --predicate 'subsystem == "app.tempoapp.Tempo"' --level debug
Useful to watch the score’s rule evaluation in real time.
5. What did Tempo see historically? — grep the rolling file log:
grep -h com.home-assistant ~/Library/Application\ Support/Tempo/Logs/tempo-*.log | tail -50
Every HA event Tempo has touched appears with timestamp and outcome. This is what Settings → Help → Export diagnostics bundle packages up.