unifi-protect is a library that enabled you to connect to and communicate with the Ubiquiti UniFi Protect API and ecosystem. UniFi Protect is Ubiquiti's next-generation video security platform, with rich camera, doorbell, and NVR controller hardware options for you to choose from, as well as an app which you can use to view, configure and manage your video camera and doorbells.
In short - because I use it every day to support a very popular Homebridge plugin named homebridge-unifi-protect that I maintain. I have been occasionally asked if I would consider packaging the core API library separately from the plugin so that other open source projects can take advantage of the work that's been done here to understand and decode the UniFi Protect API.
In addition, this implementation is unique: it's the first complete open source implementation of the realtime UniFi Protect update API, enabling instantaneous updates to Protect-related events. It's also the first (and to my knowledge only) complete implementation of the livestream API provided by UniFi Protect. Note: this is not the RTSP URLs that are provided by UniFi Protect controllers, but rather, true access to the H.264 datastream for any camera connected to the Protect controller.
Finally - the most significant reason that you should use this library: it's very well-tested, it is modern, and most importantly, it just works. It's quite easy to add support for UniFi Protect in your project using this library, and you can rely on the fact that the code is used by a significant population of users out there who ensure its continued robustness.
This implementation is largely feature complete. I strive to add support for meaningful features to a broad groups of people in order to avoid any unnecessary cruft and technical debt that may accrue over time.
The UniFi Protect API is undocumented and implementing a library like this one is the result of many hours of trial and error as well as community support.
- Full access to the UniFi Protect NVR JSON.
- The ability to retrieve the JSON details, including status, of any supported UniFi Protect device.
- The ability to modify the Protect NVR JSON or Protect devices.
- The ability to programmatically access the H.264 livestream for any camera. This is useful when you want lightweight access to the full camera feed without resorting to RTSP. For example, this is how homebridge-unifi-protect implements HomeKit Secure Video capabilities, resulting in a lighter weight solution than trying to read the RTSP streams for each camera from the Protect controller.
- Changelog: changes and release history of this library.
To use this library in Node, install it from the command line:
npm install unifi-protect
Documentation and examples for using this library to access UniFi Protect controllers is available here. Additionally, if you'd like to see all this in action in a well-documented, real-world example, please take a good look at my homebridge-unifi-protect project. It relies heavily on this library for the core functionality it provides.
So...how does UniFi Protect provide realtime updates? On UniFi OS-based controllers, it uses a websocket called
updates. This connection provides a realtime stream of health, status, and events that the cameras encounter - including motion events and doorbell ring events.
Reverse engineering the realtime updates API is a bit more difficult than the system events API because it's based on a binary protocol. The Protect system events API is a steady stream of JSONs published on all UniFi OS controllers over the
system websocket. It's used by more than just UniFi Protect, which makes it interesting for future exploration.
The Protect realtime updates API, however, is a binary protocol published over the
updates websocket, and until now has been undocumented. I spent time analyzing what's happening in the Protect browser webUI as well as observing the controller and various Protect versions themselves to reverse engineer what's going on. Pouring through obfuscated code is like solving a puzzle with all the pieces in front of you - you know it's all there, you're just not always sure how it fits together.
I welcome any additions or corrections to the protocol for the benefit of the community. I hope this helps others launch their own exploration and create new and interesting Protect-enabled capabilities.
This list represents all known apps that are using the realtime updates API for UniFi Protect. If you're using the information you discovered on this page for your own UniFi Protect-based solution, please open an issue and I'm happy to add a link to it below. I hope this can serve as a repository of sorts for UniFi Protect-based apps and solutions in the community.
- homebridge-unifi-protect: Seamless integration of UniFi Protect into HomeKit with support for cameras, doorbells, and more.
- Login to the UniFi Protect controller and obtain the bootstrap JSON. The URL is:
https://protect-nvr-ip/proxy/protect/api/bootstrap. You can look through protect-api.ts for a better understanding of the Protect login process and how to obtain the bootstrap JSON.
- Open the websocket to the updates URL. The URL is:
wss://protect-nvr-ip/proxy/protect/ws/updates?lastUpdateId?lastUpdateId=X. You can grab lastUpdateId from the bootstrap JSON in the prior step. You can see an example in protect-api.ts.
- Then you're ready to listen to messages. You can see an example of this in protect-nvr.ts.
Those are the basics and gets us up and running. Now, to explain how the updates API works...
UniFi OS update data packets are used to provide a realtime stream of updates to Protect. It differs from the system events API in that the system events API appears to be shared across other applications (Network, Access, etc.) while the updates events API appears to only be utilized by Protect and not shared by other applications, although the protocol is shared.
updates websocket is used by the UniFi Protect webUI and native applications to provide realtime updates back to the controller. UniFi cameras and doorbells also use a websocket to provide those same updates to the Protect controller. The
updates websocket uses a binary protocol to encode data largely to minimize bandwidth requirements, and provide access to more than JSON data, if needed.
So how does it all work? Cameras continuously stream updates to the UniFi Protect controller containing things like camera health, statistics, and - crucially for us - events such as motion and doorbell ring. A complete update packet is composed of four frames:
Header Frame (8 bytes) ---------------------- Action Frame ---------------------- Header Frame (8 bytes) ---------------------- Data Frame
Let's look at each of these.
The header frame is required overhead since websockets provide only a transport medium. It's purpose is to tell us what's coming in the frame that follows.
A packet header is composed of 8 bytes in this order:
|0||Packet Type||8||1 - action frame, 2 - payload frame.|
|1||Payload Format||8||1 - JSON object, 2 - UTF8-encoded string, 3 - Node Buffer.|
|2||Deflated||8||0 - uncompressed, 1 - deflated / compressed (zlib-based).|
|3||Unknown||8||Always 0. Possibly reserved for future use by Ubiquiti?|
|4-7||Payload Size||32||Size of payload in network-byte order (big endian).|
If the header has marked the payload as deflated (compressed), you'll need to inflate (uncompress) the payload before you can use it.
The action frame identifies what the action and category that the update contains:
|action||What action is being taken. Known actions are
|id||The identifier for the device we're updating.|
|modelKey||The device model category that we're updating.|
|newUpdateId||A new UUID generated on a per-update basis. This can be safely ignored it seems.|
The final part of the update packet is the data frame. The data frame can be three different types of data - although in practice, JSONs are all that come across, I've found. Those types are:
|1||JSON. If the action frame's
|2||A UTF8-encoded string.|
updateactions are always tied to any valid modelKey that exists in the bootstrap JSON. The exception is
eventswhich is tied to the Protect events history list that it maintains. The supported modelKeys from the bootstrap JSON are:
addactions are always tied to the
eventmodelKey and indicate the beginning of an event item in the Protect events list. A subsequent
updateaction is sent signaling the end of the event capture, and it's confidence score for motion detection.
- This is not the same thing as motion detection. If you want to detect motion, you should watch the
cameramodelKeys, and look for a JSON that updates
lastMotion. For doorbell rings,
lastRing. The Protect events list is useful for the Protect app, but it's of limited utility to HomeKit, and it's slow relative to just looking for the
lastMotionJSON that tends to be much more timely in its delivery. If you want true realtime updates, you want to look at the
- JSONs are only payload type that seems to be sent, although the protocol is designed to accept all three.
- With the exception of
updateactions with a
event, JSONs are always a subset of the bootstrap JSON, indexed off of
modelKey. So for a
camera, the data payload is always a subset of ProtectCameraConfigInterface (see protect-types.ts).
This is mostly of interest to the true developer nerds amongst us.