Bridging vendor cloud camera alarms to Home Assistant via MQTT (without exposing API secrets)

Category suggestion: Third-party integrations / MQTT / Automation

I run a few cloud-managed IP cameras where RTSP is either unavailable or undesirable (CGNAT, rotating credentials). The vendor exposes a cloud OpenAPI; Home Assistant still wants local entities. This is how I split responsibilities.
Boundary: cloud API vs ONVIF/RTSP

  • ONVIF/RTSP/generic camera — LAN path, stream_source in YAML, Frigate-friendly, no per-vendor token dance.

  • Cloud OpenAPI — device list, live bind URLs, alarm callbacks. HA should not hold appSecret; a small sidecar service signs requests and publishes MQTT.

I use ONVIF for wired attic cams; cloud API for doorbell-class devices behind carrier NAT. If you already have Frigate pulling substreams over RTSP, keep that path—cloud live bind is for devices where RTSP never leaves the vendor's relay.

End-to-end flow in my setup

Vendor cloud --(alarm POST)--> sidecar (sign/token) --(MQTT)--> Mosquitto --> HA Core
                                    |
                                    +-- refreshes HLS URL on demand for camera entity

The sidecar is a 120-line Node service on the same host as Mosquitto. It holds appId/appSecret, calls listDeviceDetailsByPage every 15 minutes to sync device ids, and registers setMessageCallback on startup. HA never sees the cloud API directly.

Install path

  1. Deploy a custom integration or lightweight AppDaemon / Node-RED add-on in custom_components/ (or install via HACS if someone packages a generic "cloud camera bridge"—search HACS for your vendor).

  2. Store credentials in secrets.yaml:


# secrets.yaml

camera_cloud_app_id: "YOUR_APP_ID"

camera_cloud_app_secret: "YOUR_APP_SECRET"

camera_cloud_api_host: "https://YOUR_REGIONAL_HOST"

Reference in configuration.yaml:


mqtt:

broker: 127.0.0.1

# sidecar publishes to camera/alarm/#

Never put appSecret in configuration.yaml checked into git or in Lovelace.

Entities I expose

Entity Source
camera.porch Live stream via proxy (HLS); refresh URL on stream call
binary_sensor.porch_motion MQTT camera/alarm/porch/motion
sensor.porch_last_alarm MQTT retained JSON timestamp

Example MQTT sensor in configuration.yaml:

binary_sensor:
  - platform: mqtt
    name: "Porch Motion"
  unique_id: porch_motion_cloud
    state_topic: "camera/alarm/porch/motion"
    payload_on: "ON"
    payload_off: "OFF"
    device_class: motion

camera:
  - platform: generic
    name: Porch Cloud
    still_image_url: /local/porch_placeholder.jpg
    stream_source: !secret porch_stream_url  # set by sidecar automation

Automation — motion to light

automation:
  - alias: "Porch motion — lights"
    trigger:
      - platform: mqtt
        topic: "camera/alarm/+/motion"
        payload: "ON"
    action:
      - service: light.turn_on
        target:
          entity_id: light.porch_overhead
      - delay: "00:05:00"
      - service: light.turn_off
        target:
          entity_id: light.porch_overhead

Automation — MQTT notify on person detection

automation:
  - alias: "Cloud cam person notify"
    trigger:
      - platform: mqtt
        topic: "camera/alarm/#"
    condition:
      - condition: template
        value_template: "{{ 'person' in trigger.payload }}"
    action:
      - service: notify.mobile_app_pixel
        data:
          title: "Camera alert"
          message: "{{ trigger.topic }}"

HA-specific pitfall — HLS in frontend

Lovelace picture-glance / WebRTC cards often use hls.js. When you navigate away without destroying the player, some cards leak MediaSource buffers and the next live bind URL appears "stuck." Fix: use a card that calls hls.destroy() on panel unload, or proxy FLV/WebRTC through go2rtc with a stable internal URL while the cloud URL rotates underneath.

Also: do not poll the cloud every 30 s from inside HA—rate limits hit fast. Push alarms to MQTT from your sidecar; let HA subscribe.

Second pitfall — template sensors on raw JSON

Early on I fed the full alarm JSON into a template sensor without parsing msgId. Duplicate retries incremented my "alarms today" counter threefold. Now the sidecar dedupes and publishes a normalized ON/OFF on a dedicated motion topic; HA only sees clean states.

Debugging checklist

  1. mosquitto_sub -h localhost -t 'camera/alarm/#' -v — confirm sidecar publishes before touching HA.

  2. Developer tools → States: verify binary_sensor.porch_motion toggles when you wave at the camera.

  3. If stream fails but MQTT works, re-bind live URL in sidecar logs—expired URLs look like HA regressions.

Keeping secrets off the Pi that runs HA Core was the main win. The sidecar can restart without taking down automations as long as MQTT retained messages mark bridge online state.