Streaming API
The Streaming API allows your application to send commands and listen for events over a live WebSocket connection. It can be used in situations that would otherwise require polling at regular intervals, such as displaying real-time status information in a dashboard. In most other situations, the REST API and webhooks will provide superior functionality and reliability.
Setup
Since the Streaming API is powered by Socket.IO, the easiest way to interact with it is through a Socket.IO client, which is available in a variety of languages. However, any WebSocket library can also be used. The following Node.js examples show how the Streaming API can be used with both socket.io-client (the official Socket.IO client library) and ws (a generic WebSocket library).
Establishing a connection
- socket.io-client
- ws
The first step is to install the latest 2.x version of the Socket.IO client.
npm install socket.io-client@v2-latest
You can now establish a socket connection using your panel_id
and a valid panel_token
. See the code tutorial for instructions on requesting panel tokens.
const io = require("socket.io-client")
const socket = io(`wss://panel-${panel_id}.pdk.io?token=${panel_token}`)
The first step is to install the latest version of ws.
npm install ws
You can now establish a socket connection using your panel_id
and a valid panel_token
. See the code tutorial for instructions on requesting panel tokens.
const WebSocket = require("ws")
const socket = new WebSocket(
`wss://panel-${panel_id}.pdk.io/socket.io/?token=${panel_token}&transport=websocket`
)
After establishing a connection, your application will need to provide a heartbeat mechanism by pinging the server at regular intervals. This is handled automatically by Socket.IO clients. In the example below, the number 2
refers to the ping
packet type in the Engine.IO protocol.
setInterval(() => {
socket.send(2)
}, 25000)
Listening for events
- socket.io-client
- ws
Once a socket connection is established, you can begin listening for events in the access control system. The following example listens for door.request.allowed
events, which are emitted when credential holders are granted access.
socket.on("door.request.allowed", (event) => {
console.log(event)
})
Once a socket connection is established, you can begin listening for events in the access control system. The following example listens for door.request.allowed
events, which are emitted when credential holders are granted access. This example also removes the packet type code that is automatically prepended to WebSocket messages sent by Socket.IO servers.
socket.on("message", async (event) => {
event = event.replace(/^\d*/, "") // Remove the packet type code
if (event) {
const parts = JSON.parse(event)
const topic = parts[0]
const body = parts[1]
if (topic == "door.request.allowed") {
console.log(body)
}
}
})
When an event occurs, the event is passed into the callback function provided to the event listener. The body of the event includes details related to the corresponding event topic. In this example, the event topic is door.request.allowed
and the body includes details about the person
(the credential holder) and the door
(the device that was accessed).
{
"id": "0153463f-d9ba-435f-b099-60f2b7c8d7e3",
"topic": "door.request.allowed",
"body": {
"doorId": 1,
"personId": 1,
"requestFactor": "PIN_ONLY",
"person": {
"id": 1,
"firstName": "Postman",
"lastName": "Test",
"enabled": true,
"partition": 0,
"activeDate": null,
"expireDate": null,
"metadata": {}
},
"door": {
"id": 1,
"name": "Postman Test",
"port": 1,
"metadata": {},
"connection": 1,
"partitions": [0]
}
},
"occurred": "2023-05-03 19:04:55",
"metadata": {
"occurred": 1683165895729,
"doorName": "Postman Test",
"personName": "Postman Test"
}
}
Sending commands
- socket.io-client
- ws
You can also send commands to the access control system. The following example opens a given device for 3 seconds.
socket.emit("command", {
id: crypto.randomUUID(),
topic: "door.try.dwellopen",
body: {
doorId: device_id,
dwell: 30
}
})
You can also send commands to the access control system. The following example opens a given device for 3 seconds.
socket.on("open", (event) => {
const packet_type = 42 // The packet type code for message events
socket.send(
packet_type +
JSON.stringify([
"command",
{
id: crypto.randomUUID(),
topic: "door.try.dwellopen",
body: {
doorId: device_id,
dwell: 30
}
}
])
)
})
The required id
property can be used along with the command.result.succeed event in order to confirm that a particular command has been successfully executed.
Renewing the panel token
- socket.io-client
- ws
When an event occurs after a panel token has expired, an invalidToken
event will be emitted in its place. You can renew panel tokens automatically by listening for invalidToken
events and issuing the renewedToken
command. Once a new token is received, the Streaming API will immediately emit any events that occurred while the previous token was expired.
socket.on("invalidToken", async (event) => {
const panel_token = await get_panel_token()
socket.emit("renewedToken", { token: panel_token })
})
When an event occurs after a panel token has expired, an invalidToken
event will be emitted in its place. You can renew panel tokens automatically by listening for invalidToken
events and issuing the renewedToken
command. Once a new token is received, the Streaming API will immediately emit any events that occurred while the previous token was expired.
socket.on("message", async (event) => {
event = event.replace(/^\d*/, "") // Remove the packet type code
if (event) {
const parts = JSON.parse(event)
const topic = parts[0]
if (topic == "invalidToken") {
const packet_type = 42 // The packet type code for message events
const panel_token = await get_panel_token()
socket.send(packet_type + JSON.stringify(["renewedToken", { token: panel_token }]))
}
}
})
Events
In addition to the standard Socket.IO events, your application can also listen for the following access control events.
The Streaming API only supports a small subset of events. For a complete set of events, use webhooks instead.
command.result.succeed
This event is emitted when a command executes successfully.
{
"id": "15849618-57a5-4f61-99e4-fde5c7460f4c",
"topic": "command.result.succeed",
"body": {
"commandId": "c4a4bd80-1ffa-4041-80b4-f2b3d7770294"
},
"occurred": "2023-05-04 17:59:16",
"metadata": {
"occurred": 1683248356916
}
}
door.alarm.forced
This event is emitted when a device has been forced open.
{
"topic": "door.alarm.forced",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.alarm.forced.cleared
This event is emitted when a forced alarm is cleared, which can be done by sending the door.alarm.forced.clear
command.
{
"topic": "door.alarm.forced.cleared",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.alarm.propped.off
This event is emitted when a device propped alarm is cleared.
{
"topic": "door.alarm.propped.off",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.alarm.propped.on
This event is emitted when a device has been propped open.
{
"topic": "door.alarm.propped.on",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.forceclose.off
This event is emitted when the force close feature is disabled on a device.
{
"topic": "door.forceclose.off",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.forceclose.on
This event is emitted when the force close feature is enabled on a device.
{
"topic": "door.forceclose.on",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.dps.closed
This event is emitted when a door position sensor (DPS) detects that a device has been closed.
{
"topic": "door.input.dps.closed",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.dps.opened
This event is emitted when a door position sensor (DPS) detects that a device has been opened.
{
"topic": "door.input.dps.opened",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.relay.off
This event is emitted when a device relay changes to an off state.
{
"topic": "door.input.relay.off",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.relay.on
This event is emitted when a device relay changes to an on state.
{
"topic": "door.input.relay.on",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.rex.off
This event is emitted when the request to exit sensor is deactivated.
{
"topic": "door.input.rex.off",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.input.rex.on
This event is emitted when the request to exit sensor is activated.
{
"topic": "door.input.rex.on",
"body": {
"doorId": 1,
"door": {
"id": 1,
"name": "Postman Test",
"connection": 1,
"port": 1,
"partitions": [0],
"metadata": {}
}
},
"occurred": "2023-05-04 17:59:16"
}
door.request.allowed
This event is emitted when a credential holder is granted access.
{
"id": "0153463f-d9ba-435f-b099-60f2b7c8d7e3",
"topic": "door.request.allowed",
"body": {
"doorId": 1,
"personId": 1,
"requestFactor": "PIN_ONLY",
"person": {
"id": 1,
"firstName": "Postman",
"lastName": "Test",
"enabled": true,
"partition": 0,
"activeDate": null,
"expireDate": null,
"metadata": {}
},
"door": {
"id": 1,
"name": "Postman Test",
"port": 1,
"metadata": {},
"connection": 1,
"partitions": [0]
}
},
"occurred": "2023-05-03 19:04:55",
"metadata": {
"occurred": 1683165895729,
"doorName": "Postman Test",
"personName": "Postman Test"
}
}
door.request.denied
This event is emitted when a credential holder is recognized but is denied access due to a deny rule.
{
"id": "3a5910ad-8635-4e56-92ac-25c6c24a5775",
"topic": "door.request.denied",
"body": {
"doorId": 1,
"personId": 1,
"requestFactor": "PIN_ONLY",
"explicit": true,
"person": {
"id": 1,
"firstName": "Postman",
"lastName": "Test",
"enabled": true,
"partition": 0,
"activeDate": null,
"expireDate": null,
"metadata": {}
},
"door": {
"id": 28,
"name": "Postman Test",
"port": 1,
"metadata": {},
"connection": 16,
"partitions": [0]
}
},
"occurred": "2023-05-04 18:49:42",
"metadata": {
"occurred": 1683251382729,
"doorName": "Postman Test",
"personName": "Postman Test"
}
}
door.request.unknown
This event is emitted when a credential is presented but the credential holder is not recognized.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.request.unknown",
"body": {
"doorId": 1,
"pin": "1234",
"door": {
"id": 1,
"name": "Postman Test",
"port": 1,
"metadata": {},
"connection": 1,
"partitions": [0]
}
},
"metadata": {
"occurred": 1683251028581,
"doorName": "Postman Test"
},
"occurred": "2023-05-04 18:43:48"
}
endpoint.discovered
This event is emitted in response to the endpoint.discover
command. One event is emitted per endpoint.
{
"topic": "endpoint.discovered",
"body": {
"ioLinkId": 1,
"address": "0013a200400a393b",
"ioLinkType": "wirelessCoordinator",
"ioLinkName": "wimac",
"doors": [
{
"id": 1,
"port": 1,
"name": "Postman Test"
}
]
}
}
endpoint.signal
This event is emitted in response to the endpoint.querysignal
command. One event is emitted per endpoint.
{
"topic": "endpoint.signal",
"body": {
"ioLinkId": 1,
"address": "0013a200400a393b",
"dbGain": 40
},
"occurred": "2023-05-04 18:43:48"
}
liveEvent
This aggregate event is emitted when auditable access events occur. Each event includes a description that can be used in human-readable event streams.
{
"info": "Person \"%s\" has requested access to the device \"%s\". Access allowed. Person used PIN for requesting access.",
"occurred": "2023-05-03 19:20:50",
"topic": "door.request.allowed",
"placeholders": [
{
"text": "Postman Test",
"id": 1,
"type": "person"
},
{
"text": "Postman Test",
"id": 1,
"type": "door"
}
],
"photo": null
}
migration.completed
This event is emitted when a migrated system has been activated.
{
"systemId": "ea5df7cb-8ea9-443d-9866-b5c44c05b46d"
}
migration.staged
This event is emitted when a migrated system has been fully staged.
{
"systemId": "ea5df7cb-8ea9-443d-9866-b5c44c05b46d"
}
migration.started
This event is emitted when a migration has been initiated. No body is provided with this event.
migration.task.completed
This event is emitted when a migration task has completed. Valid tasks are EXPORT_LEGACY_DATA
, CREATE_CUSTOMER
, CREATE_SYSTEM
, TRANSFORM_SCHEMA
, IMPORT_SYSTEM_DATA
, and ACTIVATE_SYSTEM
.
{
"task": "EXPORT_LEGACY_DATA"
}
migration.task.started
This event is emitted when a migration task has started. Valid tasks are EXPORT_LEGACY_DATA
, CREATE_CUSTOMER
, CREATE_SYSTEM
, TRANSFORM_SCHEMA
, IMPORT_SYSTEM_DATA
, and ACTIVATE_SYSTEM
.
{
"task": "EXPORT_LEGACY_DATA"
}
serverError
This event is emitted when an error occurs on the server.
{
"message": "Command is not supported."
}
Commands
The following access control commands can be sent through the Streaming API.
The Streaming API only supports a small subset of commands. For a complete set of commands, use the REST API instead.
door.actuate.close
This command closes the specified device.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.actuate.close",
"body": {
"doorId": 1
}
}
door.actuate.dnd
This command toggles the do not disturb setting on the specified device.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.actuate.dnd",
"body": {
"doorId": 1
}
}
door.actuate.forcetoggle
This command toggles the state of the specified device.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.actuate.forcetoggle",
"body": {
"doorId": 1
}
}
door.actuate.open
This command opens the specified device.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.actuate.open",
"body": {
"doorId": 1
}
}
door.alarm.forced.clear
This command clears the forced alarm for the specified device.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.alarm.forced.clear",
"body": {
"doorId": 1
}
}
door.try.dwellopen
This command opens a device for a period of time before automatically closing it. The dwell
parameter is specified in units of 0.1 seconds, so a value of 30 would cause the device to remain open for 3 seconds.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.try.dwellopen",
"body": {
"doorId": 6,
"dwell": 30
}
}
door.try.open
This command opens a device using the default dwell time.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "door.try.open",
"body": {
"doorId": 1
}
}
endpoint.discover
This command initiates the discovery of all connected wireless endpoints.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "endpoint.discover",
"body": {}
}
endpoint.querysignal
This command queries the last-hop signal strength of the specified wireless endpoint.
{
"id": "883296f4-2a11-4d01-92ea-aa77d029322f",
"topic": "endpoint.querysignal",
"body": {
"ioLinkId": 1, // The connection ID of the wireless coordinator
"address": "0013a200400a393b", // The MAC address of the wireless device
"actuateRelay": false, // Whether to actuate the signalling relay on and off
"relayDwell": 5 // How long to hold the signaling relay on
}
}