Webhooks

On This Page

Webhooks are a way for your servers to be notified of events that happen on August locks, doorbells and users.

Webhook Types

Locks POST /webhook/:lockID

  • operation:
    • detect when the lock is locked, unlocked, or its status is checked by anyone (so you always know the same state as all other checkers)
    • detect when the door is opened or closed for locks with a DoorSense™ sensor.
  • configuration: detect when the name of a lock changes.
  • battery: detect when batteries run low in locks and keypads.
  • systemstatus: detect when a lock is online of offline (i.e. reachable remotely)
  • accessmgmt: Not implemented yet. Detect role changes of user (e.g. guest to owner).

Doorbells POST /webhook/doorbell/:doorbellID

  • motiondetected : notification of motion detected by doorbell.
  • videoavailable : notification of doorbell video available.
  • buttonpush : notification of doorbell button pushed.

Users POST /webhook/user

  • lockmembership : Receive notification when this user is added to or removed from a lock.

No UserID in /user URL

We don't include the UserID in the URL because we can determine it from the access token.

Using Webhooks

All types of webhooks (locks, doorbells, and users) work with the same pattern:

  1. You set up an internet-facing service which can receive an HTTP POST (an HTTP PUT is possible for some hooks, but POST is best).
  2. You call an August API for each lock, doorbell, or user to tell us what events you want to hear and the URL to call with the event information for that lock.
  3. Time passes
  4. The event happens on the lock, doorbell or user.
  5. August makes one call to the endpoint you registered for that lock, doorbell, or user.
  6. When you no longer want or need the webhook, you call DELETE /webhook/... as appropriate to delete your webhook registration.

Using the /webhook/ and /webhook/doorbell/ APIs, each lock or doorbell can have one webhook registered per user/partner/device combination. If you call /webhook for the same user and device more than one time, the values sent in the last call will be the ones in effect.

Using the /webhook/user API, each user can have one webhook registered per user/partner combination.

Registering a Lock Webhook

Here is how to set a webhook for a specific lock. This webhook will get callbacks for all the event types listed in the notificationTypes array. In this example, we will subscribe to all the types.

Here we use cURL to register for the webhook. In your own API, you would POST the data directly. Here the POST data is being passed in via text file named webhook.json.

curl -X POST \
 -d @webhook.json \
 -H "x-august-api-key: API_Key_August_Gave_You" \
 -H "x-august-access-token: validAccessToken_You_Got_from_OAuth" \
 -H "content-type: application/json"\
 https://api-production.august.com/webhook/:LockID \
 --trace-ascii /dev/stdout

Contents of webhook.json file (which you sent with the @webhook.json above):

{
  "url": "https://my.webhook.endpoint/",
  "clientID":"client_id_given_by_August",
  "header": "name_of_header_you_expect",
  "token": "secret_you_create",
  "method": "POST",
  "notificationTypes": [
    "operation",
    "configuration",
    "battery",
    "systemstatus",
    "accessmgmt"
  ]
}

Parameters You Provide

Note that there are two parameters which you create at the time of the call to POST /webhook/:

  • header: the name of a header we will send in the callback
  • token: the value we will pass back to you via the header name you gave us.

These should have meaning to your API -- this is a way you can be sure the call is coming from August.

The output of the example cURL will be something like this:

== Info:   Trying 166.78.41.145...
== Info: Connected to api-production.august.com (166.78.41.145) port 443 (#0)
== Info: TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
== Info: Server certificate: *.august.com
== Info: Server certificate: DigiCert SHA2 Secure Server CA
== Info: Server certificate: DigiCert Global Root CA
=> Send header, 1024 bytes (0x400)
0000: POST /webhook/:LockID HTTP/1.1
0039: Host: api-production.august.com
005a: User-Agent: curl/7.43.0
0073: Accept: */*
0080: x-august-api-key: API_Key_August_Gave_You
00b8: x-august-access-token: validAccessToken_You_Got_from_OAuth
00f8: ...
03b8: ...
03c9: content-type: application/json
03e9: Content-Length: 280
03fe:
=> Send data, 280 bytes (0x118)
0000: {"url": "https://my.webhook.endpoint/", "client
0040: ID":"client_id_given_by_August", "header": "name_of_header_you_expect",
0080: "method": "POST", "notificationTypes": [ "operation",
00c0: "systemstatus", "battery", "accessmgmt", "configuration" ], "tok
0100: en": "secret_you_create"}
== Info: upload completely sent off: 280 out of 280 bytes
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 30 bytes (0x1e)
0000: Server: nginx/1.4.6 (Ubuntu)
<= Recv header, 37 bytes (0x25)
0000: Date: Fri, 05 Feb 2016 21:31:08 GMT
<= Recv header, 32 bytes (0x20)
0000: Content-Type: application/json
<= Recv header, 20 bytes (0x14)
0000: Content-Length: 21
<= Recv header, 24 bytes (0x18)
0000: Connection: keep-alive
<= Recv header, 785 bytes (0x311)
0000: x-august-access-token:
0040: ...
0300: ...
<= Recv header, 21 bytes (0x15)
0000: x-response-time: 10
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 21 bytes (0x15)
0000: {"message":"success"}
== Info: Connection #0 to host api-production.august.com left intact
{"message":"success"}

Obviously you are really looking for that response in your app, {"message":"success"} when you register the webhook.

Note

You can register the same webhook for multiple locks, but you will have to call the API once for each lock. When we make the callback we will include the LockID in the payload so you can tell which lock had the event.

Warning

August will only call the webhook once for each event, and we will not follow any redirections (HTTP 302).


Check signatures

All webhooks have a signature in each event's X-August-Signature header. This allows you to verify that the events were sent by August, 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, August includes a timestamp in the X-August-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 payload. We recommend a default tolerance of 5 minutes.

Verifying Signatures

August generates signatures using a hash-based message authentication code (HMAC) with SHA-256.

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:

  • timestamp as a string
  • the character .
  • The stringified JSON payload (the request body).

Step 3. Generate the expected signature

Compute an HMAC with the SHA256 hash function. Using 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 Webhooks

operation (lock or unlock)

The lock is locked or unlocked.

with August app or API

The lock was operated with the August app or the API.

HTTP Headers
Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature
Body
{
  "LockID": ":LockID", // the same :LockID you used to register the webhook
  "EventType": "operation",
  "Event": "unlock", // could also be "lock"
  "Device": "lock",
  "User": { // information about the user that cause the event
    "UserID": ":userID", // the August user identifier
    "FirstName": "Andy",
    "LastName": "Rothfusz",
    "PhoneNo": "+15555555555", // user's verified phone number
    "Email": "user@example.com", // user's verified email address
    "imageInfo": null // this could include URLs to unverified images of the user
  }
}

with Keypad

The lock was operated via the keypad.

HTTP Headers
Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature
Body
{
  "LockID": ":LockID", // the same :LockID you used to register the webhook
  "EventType": "operation",
  "Event": "unlock", // could also be "lock"
  "Device": "keypad",
  "User": { // information about the user that cause the event
    "UserID": ":userID", // the August user identifier
    "FirstName": "Andy",
    "LastName": "Rothfusz",
    "PhoneNo": "+15555555555", // user's verified phone number
    "Email": "user@example.com", // user's verified email address
    "imageInfo": null // this could include URLs to unverified images of the user
  }
}

manually

Someone turned the lock manually.

HTTP Headers
Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature
Body
{
  "LockID": ":LockID", // the same :LockID you used to register the webhook
  "Device": "keypad",
  "EventType": "operation",
  "Event": "unlock", // could also be "lock"
  "User": { // information about the user that cause the event
    "UserID": "manualunlock", // could also be "manuallock"
  }
}

operation (door opened or closed)

The door is opened or closed (for locks with a DoorSense™ sensor).

HTTP Headers

Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature

Body

{
  "LockID": ":LockID", // the same :LockID you used to register the webhook
  "EventType": "operation",
  "Event": "open", // could also be "closed" or "init" or "unknown"
  "Device": "lock",
  "User": { // information about the user that cause the event
    "UserID": "DoorStateChanged"
  }
}

operation (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
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature

Body

{
  "LockID": "1A2B...",
  "EventType": "status", // status means it is the current state, operation means it is a new state
  "Event": "lock", // The current state of the lock
  "Device": "lock",
  "User": { // Which user checked the lock status
    "UserID": "some-user-id",
    "FirstName": "Some",
    "LastName": "User",
    "PhoneNo": "+15555555555",
    "Email": "someone@example.com",
    "imageInfo": {
      "original": {
        "width": 640,
        "height": 480,
        "format": "jpg",
        "url": "https://example.com/some.jpg",
        "secure_url": "https://example.com/some.jpg"
      },
      "thumbnail": {
        // similar to 'original'
      }
    }
  }
}

configuration

If you create a webhook requesting callbacks for configuration changes, you will now get notified of name changes. This can help you to refer to a lock in your application by the same name the user sets in the August application.

HTTP Headers

Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature

Body

{
  "LockID": "ABC123",
  "EventType": "configuration",
  "Event": "lock_name_changed",
  "Lock": {
    "Name": "new lock name"
  },
  "User": {
    "UserID": "abcd-1234",
    "FirstName": "John",
    "LastName": "Smith",
    "PhoneNo": "+165011122222",
    "Email": "john@smith.com",
    "imageInfo": {
      // ...
    }
  }
}

systemstatus

It can take several minutes (~5) for the August servers to decide that a lock is offline, so this webhook will not fire immediately when the lock disconnects. However the server will send out a notification as soon as it sees the lock come back online.

HTTP Headers

Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature

Body

{
  "EventType": "systemstatus",
  "LockID": ["4F206CBE466645379CDF5F39FB992683"],
  "Event": "online"
}
Event Meaning
online The lock is online.
offline The lock is offline.

battery (keypad)

The battery webhook fires when the August 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.

Warning

This battery callback JSON will be changing to make it more consistent. See Future "Consistent" format below.

HTTP Headers

Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json
X-August-Signature: signature

Body

{
  "EventType": "battery",
  "LockID": "4F206CBE466645379CDF5F39FB992683",
  "Event": "keypad_battery_critical",
  "DeviceType": "keypad",
  "DeviceSerialNumber": "K1GJW0004G"
}
Event Meaning
keypad_battery_none no action required by owner
keypad_battery_warning batteries are running low
keypad_battery_critical batteries need replacement immediately or the user risks the keypad not working

battery (lock)

HTTP Headers

Content-Length: 252
Connection: keep-alive
Name-Of-Header-You-Expect: secret_you_create
Content-Type: application/json

Body

{
  "EventType": "system",
  "LockID": "lock ID of lock with the battery event",
  "Event": "lock_battery_alert",
  "warningLevel": "lock_state_battery_warning_2week",
  "userID": "the user ID who registered the webhook"
}
Event Meaning
lock_state_battery_warning_none no action required by owner
lock_state_battery_warning_2week batteries are running low, should last about 2 weeks
lock_state_battery_warning_1week batteries should last about 1 week
lock_state_battery_warning_2day batteries need replacement immediately or the user risks the lock not working

Future "Consistent" format

We are working on a new structure for both types of battery warnings (internal issue id is ASL1-11815).

The current plan is for this format:

{
  "EventType":"battery",
  "DeviceType":"keypad|lock",
  "LockID":"lockID or the lock ID associated with the keypad",
  "UserID":"user ID of person who registered the hook",
  "Event":"battery_level_none|battery_level_warning|battery_level_critical"
}

Example Response: motiondetected (/doorbell)

{
  "DoorbellID":"54b6c08ed4c6",
  "EventType":"doorbell_motion_detected",
  "Url":"http://res.cloudinary.com/august-com/image/upload/v1545186381/bxdxlcgc00hioa98y1xw.jpg",
  "SecureURL":"https://res.cloudinary.com/august-com/image/upload/v1545186381/bxdxlcgc00hioa98y1xw.jpg",
  "Width":480,
  "Height":640
}

The motion detected event includes a still image taken at the time of the motion.

Example Response: buttonpush (/doorbell)

{
  "DoorbellID":"54b6c08ed4c6",
  "EventType":"buttonpush",
  "dvrID":"d865a29e-cbd6-4b80-8944-8935d217757e"
}

Example Response: videoavailable (/doorbell)

{
  "DoorbellID":"54b6c08ed4c6",
  "EventType":"doorbell_video_upload_available",
  "dvrID":"5714bff4-5a94-4780-bda4-f94026e2a715",
  "cause":"doorbell_motion_detected",
  "startTime":1545187132470
}

To get the URL to the video, you must call GET /doorbells/:doorbellID/videoevent?dvrID=<dvrID> which will return

{
  "url": "https://something.cloudfront.net/<uuid>/<doorbellid>/<uuid>/playlist.m3u8"
}

Example Response: lockmembership (/user)

// User was added to a lock
{
  "UserID":"cfa91435-41b0-4632-877b-339dfaf4a445",
  "EventType":"authorization",
  "Event":"lock_user_add",
  "LockID":"4F206CBE466645379CDF5F39FB992683"
}

// User was removed from a lock
{
  "UserID":"cfa91435-41b0-4632-877b-339dfaf4a445",
  "EventType":"authorization",
  "Event":"lock_user_remove",
  "LockID":"4F206CBE466645379CDF5F39FB992683"
}