[How-To] Correctly Integrate UniFi Protect Webhooks with OpenHAB Using the Webhook Binding

Webhooks with OpenHAB Using the Webhook Binding

Hi everyone,
After a lot of testing and troubleshooting, I wanted to share a full, working guide on how to integrate UniFi Protect webhook events into OpenHAB — specifically for the new fingerprint scanner and NFC tag reader features available on the UniFi G4 Doorbell Pro.

My goal was to automatically trigger OpenHAB automations when a fingerprint or NFC badge scan was recognized at the doorbell — for example, to unlock a smart lock or disarm an alarm — without needing any external proxy servers, header rewrites, or extra software.

This guide explains why direct REST API methods fail, and shows the correct setup using the Webhook Binding.


Why It Matters

At first, I tried using OpenHAB’s native REST API to command items directly from UniFi Protect webhooks.
However:

  • UniFi Protect webhooks only allow GET or POST methods.
  • PUT, which OpenHAB would accept for state updates, is not supported by UniFi Protect.
  • POSTs from UniFi Protect always have Content-Type: application/json, and you cannot customize headers.
  • OpenHAB REST API expects Content-Type: text/plain when POSTing commands.
  • GET requests are allowed by UniFi, but GET cannot update items (GET is read-only).

Result:
Direct REST API calls from UniFi Protect cannot reliably command OpenHAB items because of method and Content-Type mismatches.


The Solution: Use the Webhook Binding

The Webhook Binding in OpenHAB:

  • Accepts POSTs regardless of Content-Type.
  • Does not need authentication headers.
  • Lets you react flexibly inside OpenHAB.

However, some quirks exist (described below).


Step-by-Step Setup

1. Install the Webhook Binding

  • Open OpenHAB UI → Settings → Bindings → Install Webhook Binding.

2. Create the Webhook Thing (Text File)

Create a Thing in your .things file:

Thing webhook:webhook:unifi_webhook "UniFi Webhook" [
  id="unifi_webhook",
  expression="req.body"
]

Important:
You must specify an expression, even if you don’t actually use it.
(req.body or resp.status=200 both work.)

This creates a webhook endpoint at:

http://[your-openhab-ip]:8080/webhook/unifi_webhook

3. Use the Built-In LastCall Channel (No Manual Channel Creation)

The Webhook Thing automatically provides a built-in lastCall channel.
This is a DateTime value that updates whenever the webhook is received.

You do not need to manually create any additional channels.

Simply link the lastCall channel to a DateTime Item:

DateTime UniFi_Protect_Webhook_Last_request { channel="webhook:webhook:unifi_webhook:lastCall" }

4. Set Up a Rule Based on LastCall Changes

You can trigger automation when the webhook fires by watching for changes to the lastCall item.

Example rule:

rule "UniFi Webhook Trigger via LastCall Item"
when
  Item UniFi_Protect_Webhook_Last_request changed
then 
  logInfo("Fingerprint", "Scan matched – unlocking front door…")
  
  // Send the UNLOCK command to your lock
  sFrontDoorLockSwitch.sendCommand(OFF)
  
end

5. Set Up Webhook in UniFi Protect

Inside UniFi Protect:

  • Method: POST
  • URL:
http://[your-openhab-ip]:8080/webhook/unifi_webhook
  • Headers: (None — UniFi does not allow custom headers)
  • Body: automatically generated by protect and ignored in this guide.

Now, whenever the event happens (fingerprint, motion, etc.), the webhook will fire and OpenHAB will update the lastCall item.


Important Technical Summary

HTTP Method Supported by UniFi Protect? Can Update OpenHAB? Problem
GET Yes No (read-only) Cannot command items
POST Yes Normally yes, but uses wrong Content-Type (application/json)
PUT No Yes (but UniFi cannot send PUT)

Final Notes

  • Webhook Things can be defined in text files.
  • No manual channels are needed — just use the built-in lastCall.
  • UniFi Protect webhooks only support POST and GET (no PUT).
  • POSTs always use application/json, and headers cannot be customized.
  • Using the lastCall channel is the easiest and most reliable method to detect webhook triggers.

Thus, the Webhook Binding is the cleanest and most reliable method to integrate UniFi Protect with OpenHAB.

5 Likes

Great analysis @Martin_Horsfield!

I’m using the unifi protect marketplace binding UniFi Protect Binding (Cloudkey gen2+, Dream Machine Pro, NVR), and I am curious on how your approach differs (except for using the webhook binding and not needing to code java)?

Brg

Thanks for this wonderful information, it helps me.

This works for me with the standard API:

I guess it is not useful if you can use regular post requests. The unifi Android app doesn’t appear to allow for plain text post. I guess I should have tried the web version first.

So annoying when Android does not receive the same support as iOS

I think the web version is also incomplete: I could never find a way to set a body there. But maybe this changed in a newer version.

But at least you could also set a header there. However a POST without a body is pretty pointless.

So with only Android phone/WWW page you cant change the content type to be able to use it without the additional binding? ( I can’t use it as my house since 7years is running on OH2.4, with general rule if it ain’t broke, don’t fix it)

I just got my G6 camera to do ALPR at the gate to replace my old uber hi-maitenance solution based on the RPI running motion eye that took about 3s to analyze the frame.

But from I read here it might end up with writing python webhook-listener that will extract the data from flawed protect webhook implementation into plain mqtt publish.

It seems that you can set the content type (add header button), but I don’t see a field for the message body :man_shrugging:

I really don’t get why they don’t add this field in the web UI?!

1 Like

So I crafted a quick webhook_listener in python that gets protect webhooks and sends me mqtt message and I found that Protect functionality is totally FLAWED and useless for automation.

I tested extensively their face detection functionality for now. The problem is that webhook is generated when the face leaves the frame! So I enter frame 15h:42m:22s at :25s the protect playback gets the face and displays it with 85% confidence as I approach the gate.

I stand there like stupid for 30seconds, thinking where did I made coding mistake. :43m:02s I leave the frame and bang here comes a webhook exactly 43m:07.980s

So forget you automate anything with those cameras using those WebHooks. Ubiquity does have their Access components that do claim to have that working but they must be using some code path that is not available for us.

I got chat to convert the timestamps, apart from that it is the original.

{
  "alarm": {
    "name": "Person of interest",
    "sources": [],
    "conditions": [
      {
        "condition": {
          "type": "is",
          "source": "face_known",
          "value": "681e47f000382403e41fae12"
        }
      }
    ],
    "triggers": [
      {
        "device": "847848540D66",
        "value": "Maciej Eckstein",
        "key": "face_known",
        "group": {
          "name": "Maciej Eckstein"
        },
        "eventId": "6820a94003199203e415e945",
        "timestamp": 1746970986798,
        "timestamp_human": "2025-05-11T13:43:06.798Z"
      }
    ],
    "eventPath": "/protect/events/event/6820a94003199203e415e945",
    "eventLocalLink": "https://172.16.10.109/protect/events/event/6820a94003199203e415e945"
  },
  "timestamp": 1746970987980,
  "timestamp_human": "2025-05-11T13:43:07.980Z"
}

Edit:
I now do the tests with the car and ALPR and from first test it looks the LPR is faster. I’ll report after my wife comes back from the shop.

:face_with_peeking_eye:

I only use it for my doorbells nfc and fingerprint sensors which works fine most of the time. Sometimes it just stops to work completely though. Then I have to reboot the udm to get it working again.

Ok I did couple more tests with LPR and it is indeed faster and usable. A trick is also to to make a detection start closer to camera as if you leave it full screen it will detect a plate early and sometimes with errors, and will never make a correction even if the car is approaching.


Body RAW:
 {"alarm":{"name":"Vehicle of interest","sources":[],"conditions":[{"condition":{"type":"is","sourcenown"}},{"condition":{"type":"is","source":"license_plate_of_interest"}}],"triggers":[{"device":"847"},"eventId":"6820e7a7011c9203e418969d","timestamp":1746986919682}],"eventPath":"/protect/events/eveents/event/6820e7a7011c9203e418969d"},"timestamp":1746986920773}
---------------
2025-05-11 20:08:40
Key: license_plate_known, Name: BMW i3, Timestamp: 2025-05-11 20:08:39

looking at MQTT

2025-05-11 20:08:40.875575 gate/plate WW5315J
2025-05-11 20:08:40.900931 gate/relay0/command 1

and this is the detection zone that made a trick