Webhooks
Webhooks are the primary way for you to be notified of events that happen on locks, keypads, and users.
All types of webhooks (locks, keypads, and users) work with the same pattern:
- You set up an internet-facing service which can receive an HTTP
POST. - You register for webhooks when a user links their account to Yale Home using OAuth.
- Time passes.
- An event happens.
- Yale Home sends one webhook to the URL you registered.
When you no longer want or need it, delete the webhook registration.
Registering for webhooks#
Locks#
Use this endpoint to register for webhooks about lock related events. If the lock has a keypad, you will also receive webhooks for keypad related events.
Each lock can have one webhook registered per user. If you call this endpoint again for the same user, then the existing webhook registration will be overwritten.
The following example creates a webhook registration for events of type operation and battery.
POST /webhook/:lockID
{
"url": "https://my.webhook.endpoint/",
"method": "POST",
"clientID": "your_client_id",
"header": "name_of_header_you_expect",
"token": "secret_you_create",
"notificationTypes": [
"operation",
"battery"
]
}
When an event happens, we send a webhook to your url.
Note that there are two parameters which you create at the time of the call:
header: the name of a header we will send in the webhook.token: the value we will pass back to you via theheadername you gave us.
These should have meaning to your API -- this is a way for you to pass parameters to your webhook, or ensure the call is coming from Yale Home.
Users#
Use this endpoint to register for webhooks about user related events.
Each user can have one webhook registered. If you call this endpoint again for the same user, then the existing webhook registration will be overwritten.
POST /webhook/user
{
"url": "https://my.webhook.endpoint/",
"method": "POST",
"clientID": "your_client_id",
"header": "name_of_header_you_expect",
"token": "secret_you_create",
"notificationTypes": [
"lockmembership",
"lifecycle"
]
}
Deleting webhook registrations#
Delete a webhook registration to stop receiving webhooks.
Locks#
Use this endpoint to delete a lock webhook registration.
DELETE /webhook/:lockID/:clientID
Users#
Use this endpoint to delete a user webhook registration.
DELETE /webhook/user
Webhook event types#
Locks#
- operation:
- The lock was locked, unlocked, unlatched, or its status was checked by anyone.
- The door was opened or closed for locks with a DoorSense™ sensor.
- operation.type:
- The lock was locked, unlocked by BLE (includes linked lock) and PIN/Finger/RFID etc.
- configuration:
- The name of the lock changed.
- A keypad PIN was managed (created, updated, or deleted)
- battery: The batteries are low in the lock or its keypad.
- systemstatus: The lock went offline or came offline (i.e. reachable remotely)
- accessmgmt:
- A user was added or removed from the lock.
- A user's access level was changed on the lock. (e.g. from guest to owner).
- A user's access schedule was changed on the lock. (e.g. from always to temporary)
Users#
- lockmembership: The user was added to or removed from a lock.
- lifecycle: The user deleted their account.
Retries#
Yale Home will only call a webhook once for each event, and will not follow any re-directions (HTTP 302). If the response from your web server to a webhook does not have a HTTP 2xx status code, we will not retry sending it.
Checking signatures#
Every webhook has a signature in the X-Signature header. This signature allows you to verify that the webhook was sent by Yale Home, not by a third party.
Preventing replay attacks#
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them.
To mitigate such attacks, Yale Home includes a timestamp in the X-Signature header.
Because this timestamp is part of the signed payload, it is also verified by the signature,
so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but
the timestamp is too old, you can have your application reject the webhook. We recommend a default
tolerance of 5 minutes.
How to check a signature#
Yale Home generates signatures using a hash-based message authentication code (HMAC) with SHA-256, signed with your API key.
Step 1. Extract timestamp and signature from the header
Split the header, using the , character as the separator, to get a list of elements.
Then split each element, using the = character as the separator, to get a prefix and value pair.
The value for the prefix t corresponds to the timestamp, and v corresponds to the signature.
Step 2. Create the payload string
The payload_string is created by concatenating the following:
timestampas a string- the character
. - The stringified JSON payload (the request body).
Step 3. Generate the expected signature
Compute an HMAC with the SHA-256 hash function. Use your API key as a signing secret and
the payload_string as the message.
Step 4. Compare the expected and actual signature Compare the signature in the header to the expected signature. Compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.
Example lock webhooks#
For the following webhook examples, Timestamp is when the event happened. Say the user unlocked via the keypad. The lock saves the timestamp then.
At some point in the future, the log for that event is sent to our system through the wifi bridge. We then set timeStamp to when we send the webhook.
Lock or unlock#
The lock was locked or unlocked.
with Yale Home app or API#
The lock was operated with the Yale Home app or the API.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "operation",
"Event": "unlock",
"Device": "lock",
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
| Event | Meaning |
|---|---|
unlock |
The lock was unlocked. |
lock |
The lock was locked. |
unlatch |
The lock was unlatched |
with Keypad#
The lock was operated via the keypad.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "operation",
"Event": "unlock",
"Device": "keypad",
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
| Event | Meaning |
|---|---|
unlock |
The lock was unlocked. |
lock |
The lock was locked. |
unlatch |
The lock was unlatched |
manually#
Someone turned the lock manually.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1662762147868,
"EventID": "192fda30-9062-4301-822e-12829578ac67",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "operation",
"Event": "unlock",
"Device": "lock",
"User": {
"UserID": "manualunlock"
},
"Timestamp": 1662762142000
}
| Event | Meaning |
|---|---|
unlock |
The lock was unlocked. |
lock |
The lock was locked. |
unlatch |
The lock was unlatched |
| UserID | Meaning |
|---|---|
manualunlock |
The lock was unlocked/unlatched. (Note: same user id for unlock and unlatch) |
manuallock |
The lock was locked. |
Door opened or closed#
The door was opened or closed (for locks with a DoorSense™ sensor).
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "operation",
"Event": "open",
"Device": "lock",
"User": {
"UserID": "DoorStateChanged"
}
}
| Event | Meaning |
|---|---|
closed |
The door was closed. |
open |
The door was opened. |
ajar |
The door was opened very slightly. |
Lock status#
The Event tells you the state of the lock which the status check returned.
So operation webhooks will now send you two EventType: "operation" and
"status". The "operation" fires as soon as the actual event happens. The
"status" fires whenever anyone calls /remoteoperate/status and notifies all
listeners on that lock of the current status.
That way you can reduce the number of times you call status yourself. You
always know the latest state of the lock, at least as well as any other
listeners do.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "status",
"Event": "lock",
"Device": "lock",
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
| Event | Meaning |
|---|---|
unlock |
The lock is unlocked. |
lock |
The lock is locked. |
unlatch |
The lock was unlatched |
Lock name change#
The user changed the name of the lock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "configuration",
"Event": "lock_name_changed",
"Lock": {
"Name": "new lock name"
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
Lock battery warning#
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
name_of_header_you_expect: secret_you_create
Body
{
"EventType": "system",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"Event": "lock_battery_alert",
"warningLevel": "lock_state_battery_warning_2week",
"userID": "4337d8c6-0fda-4068-989c-aba166ae6b9d"
}
userID: The ID of the user who registered for the webhook.
| warningLevel | Meaning |
|---|---|
lock_state_battery_warning_none |
The lock's batteries are high. |
lock_state_battery_warning_4week |
The lock's batteries should last about 4 weeks more. |
lock_state_battery_warning_2week |
The lock's batteries should last about 2 weeks more. |
lock_state_battery_warning_1week |
The lock's batteries should last about 1 week more. |
lock_state_battery_warning_2day |
The lock's batteries should last about 2 days more. |
Lock clock is wrong#
The lock sent a log to the Yale API that has a timestamp that is in the future or too far in the past. Most likely the batteries ran out at some point in the past and the lock's real time clock is now wrong. Connect to the lock with the Yale app or the Yale SDK to correct it's clock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
name_of_header_you_expect: secret_you_create
Body
{
"EventType": "systemstatus",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"Event": "lock_log_timestamp_drifted",
"warningLevel": "lock_state_battery_warning_2week",
"Timestamp": 1678750917002,
"TimestampDrifted": 2487412339000
}
TimestampDrifted: The timestamp from the lock, which is either in the future or too far in the past.Timestamp: The timestamp of when Yale sent the webhook.
Keypad PIN managed#
A keypad PIN was added to the lock, disabled on the lock, or enabled on the lock. These webhooks apply to PINs that were managed with the Yale Home mobile app. PINs that were managed on the lock using the API have a separate webhook system.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "configuration",
"Event": "keypad_pin_managed",
"Lock": {
"Name": "Front Door"
},
"Pin": {
"state": "load",
"action": "intent"
},
"PinUser": {
"UserID": "2d82e357-c2ec-4c37-9127-ad867b1bde7f",
"FirstName": "Another",
"LastName": "User",
"PhoneNo": "+14085551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
Useris the lock owner who added the PIN to the lock.PinUseris the user who the PIN is assigned to.
| state | Meaning |
|---|---|
load |
A PIN was added to the lock. |
disable |
A PIN was disabled. |
enable |
A PIN was enabled. |
delete |
A PIN was deleted. |
Keypad master PIN managed#
The master PIN was changed on the lock. This applies to locks with built-in keypads, like the Yale Assure Lock 2.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1675813513629,
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "configuration",
"Event": "keypad_pin_managed",
"EventID": "912efb36-5b24-473f-ab27-f4c9b057deb0",
"Lock": {
"Name": "Front Door"
},
"Pin": {},
"PinUser": {
"UserID": "masterpin",
"FirstName": "Master",
"LastName": "PIN",
"PhoneNo": "n/a",
"Email": "n/a",
"imageInfo": "n/a"
},
"User": {
"UserID": "masterpin",
"FirstName": "Master",
"LastName": "PIN",
"PhoneNo": "n/a",
"Email": "n/a",
"imageInfo": "n/a"
},
"Timestamp": 1675813513574
}
Keypad battery warning#
The battery webhook fires when the Yale Home servers learn that the batteries on a device are running low enough that the owner should be notified.
Keypads and locks can both run low on batteries.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"EventType": "battery",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"Event": "keypad_battery_critical",
"DeviceType": "keypad",
"DeviceSerialNumber": "K1G0000001"
}
| Event | Meaning |
|---|---|
keypad_battery_none |
The keypad's batteries are high. |
keypad_battery_warning |
The keypad's batteries are running low. |
keypad_battery_critical |
The keypad's batteries need replacing immediately or the it may not work. |
Wifi bridge went offline or online#
The Connect Wi-Fi Bridge associated with a lock either went offline or came online.
It takes about 5 minutes for the Yale Home servers to decide that a bridge is offline, so this webhook will not fire immediately when the bridge goes offline. However, the server will send a webhook as soon as it sees the bridge come back online.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"EventType": "systemstatus",
"LockID": ["1234567890ABCDEF1234567890ABCDEF"],
"Event": "online"
}
| Event | Meaning |
|---|---|
online |
The bridge went online. |
offline |
The bridge came offline. |
User added to the lock#
A user was added to the lock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "authorization",
"Event": "lock_user_add",
"Lock": {
"Name": "Front Door"
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
User removed from the lock#
A user was removed from the lock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "authorization",
"Event": "lock_user_remove",
"Lock": {
"Name": "Front Door"
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
}
}
}
User access level changed on lock#
A user's access level was changed on the lock. For example, from guest to owner.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663970396958,
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "authorization",
"Event": "lock_usertype_changed",
"Lock": {
"Name": "Front Door"
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
},
"UserType": "user"
},
"EventID": "44387d09-25e8-4529-96bc-bcb9088e5045",
"Timestamp": 1663970396948
}
| UserType | Meaning |
|---|---|
user |
The user is now a guest. |
superuser |
The user is now an owner. |
User access schedule changed on lock#
A user's access schedule was changed on the lock. For example, from always to temporary.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663970396958,
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventType": "authorization",
"Event": "lock_accesstype_changed",
"Lock": {
"Name": "Front Door"
},
"User": {
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"FirstName": "Example",
"LastName": "User",
"PhoneNo": "+16505551212",
"Email": "user@example.com",
"imageInfo": {
"original": {
"width": 640,
"height": 480,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
},
"thumbnail": {
"width": 128,
"height": 128,
"format": "jpg",
"url": "https://example.com/some.jpg",
"secure_url": "https://example.com/some.jpg"
}
},
"AccessType": "rule_access_always"
},
"EventID": "44387d09-25e8-4529-96bc-bcb9088e5045",
"Timestamp": 1663970396948
}
| AccessType | Meaning |
|---|---|
rule_access_always |
The user may always use the lock. |
rule_access_temporary |
The user's access is temporary. |
rule_access_recurring |
The user's access is recurring. |
Example user webhooks#
User added to a lock#
The user was added to a lock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663019240123,
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"EventType": "authorization",
"Event": "lock_user_add",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventID": "bfd056a3-b9e5-4640-b46e-a24c837b4102",
"Timestamp": 1663019240070
}
User removed from a lock#
The user was removed from a lock.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663018978098,
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"EventType": "authorization",
"Event": "lock_user_remove",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventID": "d5ed4c3c-3464-4595-b1f4-7b5fff4d922f",
"Timestamp": 1663018978087
}
User access level changed#
The user's access level was changed on a lock. For example, from guest to owner.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663019320159,
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"UserType": "superuser",
"EventType": "authorization",
"Event": "lock_usertype_changed",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventID": "654a2c1e-342a-4b6d-8ae1-44d2f0e4bed2",
"Timestamp": 1663019320114
}
| UserType | Meaning |
|---|---|
user |
The user is now a guest. |
superuser |
The user is now an owner. |
User access schedule changed#
The user's access schedule was changed on a lock. For example, from always to temporary.
HTTP Headers
Content-Length: 252
Connection: keep-alive
Content-Type: application/json
X-Signature: signature
name_of_header_you_expect: secret_you_create
Body
{
"timeStamp": 1663019320159,
"UserID": "4337d8c6-0fda-4068-989c-aba166ae6b9d",
"EventType": "authorization",
"Event": "lock_accesstype_changed",
"AccessType": "rule_access_always",
"LockID": "1234567890ABCDEF1234567890ABCDEF",
"EventID": "654a2c1e-342a-4b6d-8ae1-44d2f0e4bed2",
"Timestamp": 1663019320114
}
| AccessType | Meaning |
|---|---|
rule_access_always |
The user may always use the lock. |
rule_access_temporary |
The user's access is temporary. |
rule_access_recurring |
The user's access is recurring. |