For months I had Home Assistant automations that would fail silently because they assumed a device was present when it wasn’t. A motion sensor on the second floor. A Zigbee repeater. A guest’s phone. The automations couldn’t tell me whether the thing they were trying to reach actually existed in my network anymore. I’d find out three days later when someone said “hey, why didn’t the light turn on?”

The obvious answer was to check NetBox first—I already document everything there—before firing off commands. But that meant writing the glue code to pull from NetBox, parse it, and feed it into Home Assistant in a way that wouldn’t fall apart the next time I reorganized a room.
Why the integration mattered at all
Home Assistant already has device discovery. DHCP scanning. mDNS. But none of those are reliable in a homelab where you have temporary equipment, guest networks, and infrastructure that goes up and down. NetBox is where I actually say “this device exists and is supposed to be here.” It’s the source of truth. Home Assistant, for all its strengths, is good at reacting to what it sees—not at knowing what should be there.
The integration I needed was simple in concept: query NetBox for all my devices, extract the ones relevant to Home Assistant, and expose them as entities that automations could reference. No complex webhook setup. No constant syncing. Just a scheduled pull that runs every fifteen minutes.
The setup that actually works
NetBox exposes a REST API. Home Assistant can call it. I wrote a Python script that lives in an AppDaemon container and does the work.
First, the NetBox side is trivial. I created an API token in NetBox’s admin UI—Settings → Tokens—and gave it read-only permissions. Then I wrote a simple HTTP request to list all devices:
curl -H "Authorization: Token YOUR_TOKEN_HERE"
https://netbox.your-domain.com/api/dcim/devices/?limit=0
That returns a JSON payload with every device, its primary IP, site, role, and tags. I can filter by tag or status. In my case, I tagged every device that matters to automations with ha_tracked.
The AppDaemon script pulls that list, normalizes it, and creates Home Assistant input_boolean helpers for each device. Presence detected? The helper is on. Nothing’s responsive? Off.
import aiohttp
import asyncio
from datetime import datetime
class NetBoxPresenceSync:
def __init__(self, hass):
self.hass = hass
self.netbox_url = "https://netbox.your-domain.com/api"
self.netbox_token = "YOUR_TOKEN_HERE"
self.ping_timeout = 3
async def get_devices(self):
headers = {"Authorization": f"Token {self.netbox_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.netbox_url}/dcim/devices/?tag=ha_tracked&limit=0",
headers=headers
) as resp:
data = await resp.json()
return data.get('results', [])
async def sync_presence(self):
devices = await self.get_devices()
for device in devices:
device_name = device['name']
primary_ip = device.get('primary_ip4') or device.get('primary_ip6')
if primary_ip:
ip_addr = primary_ip['address'].split('/')[0]
is_up = await self.ping_host(ip_addr)
else:
is_up = False
helper_id = f"input_boolean.netbox_{device_name.lower()}"
await self.hass.services.async_call(
"input_boolean",
"turn_on" if is_up else "turn_off",
{"entity_id": helper_id}
)
async def ping_host(self, ip):
# Simple TCP connect attempt to port 22 or 443
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection(ip, 22),
timeout=self.ping_timeout
)
writer.close()
return True
except:
return False
This isn’t bulletproof. It assumes all your devices are reachable via ping or TCP. Mine mostly are—but the office printer doesn’t respond, and neither does that old UniFi controller I keep meaning to remove. I added those to a ignore_missing tag in NetBox and filter them out in the script.
Where it breaks, and why I kept it anyway
The timing was my first surprise. I expected latency. Didn’t expect that pinging 40+ devices every 15 minutes would occasionally block the entire Home Assistant system for 8-10 seconds while the checks ran. The fix was obvious—run the pings in a separate thread pool—but I spent an hour confused about why my automations would pause mid-execution.
The other issue is permission creep. If you expose device presence as Home Assistant entities, someone needs to update them. If that someone is a script, the script needs network access. It also needs NetBox access. It needs to be able to write to Home Assistant. Three different permission domains. I locked it down with local-only API tokens and network segmentation, but it’s another thing to maintain.
What I didn’t expect was how useful it became beyond the original problem. I started using device presence in automations that have nothing to do with reliability—turning off lights in rooms where no tracked device has been seen in an hour, for example. Or pausing media server jobs if certain admin devices go offline. The NetBox integration became a general presence layer, not just a failsafe.
I’m running this on AppDaemon 4.1.1 right now. The sync runs every 15 minutes, takes about 2-3 seconds with the thread pool, and hasn’t failed in three months. You’ll need your NetBox instance accessible from wherever Home Assistant is running—in my case, both are behind Traefik on the same Docker network, so it’s just a local DNS name.
The real question I haven’t answered yet is whether this should push data back to NetBox when devices change state, or if NetBox should be read-only. Right now it’s read-only. Making it bidirectional feels like it would create more problems than it solves—you’d have to decide which system wins when they disagree. But ask me again in six months.
Explore NetBox in our AI Homelab Toolkit.
Recommended Hardware & Hosting
Build your homelab with hardware tested and used by our team.
Affiliate links — we may earn a small commission at no extra cost to you.