Streaming API
The Streaming API allows your application to 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, 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 Socket.IO client.
npm install socket.io-client
You can now establish a socket connection using your system ID and a valid system token. See the code tutorial for instructions on requesting system tokens.
const io = require("socket.io-client")
const socket = io(`wss://systems.pdk.io?systemId=${systemId}&token=${systemToken}`)
The first step is to install ws.
npm install ws
You can now establish a socket connection using your system ID and a valid system token. See the code tutorial for instructions on requesting system tokens.
const WebSocket = require("ws")
const socket = new WebSocket(
`wss://systems.pdk.io/socket.io/?systemId=${systemId}&token=${systemToken}&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)
Subscribing to events
- socket.io-client
- ws
Once a connection is established, you can subscribe to event topics that are relevant to your application.
socket.on("connect", () => {
socket.emit("subscribeIsolated", {
topics: ["device.request.allowed", "device.request.denied", "device.request.unknown"]
})
})
Once a connection is established, you can subscribe to event topics that are relevant to your application.
socket.on("open", () => {
const packetType = 42 // Packet type code for message events
const topics = ["device.request.allowed", "device.request.denied", "device.request.unknown"]
const message = packetType + JSON.stringify(["subscribeIsolated", { topics: topics }])
socket.send(message)
})
Listening for events
- socket.io-client
- ws
You can now begin listening for events from the access control system. The following example listens for device.request.allowed
events, which are emitted when holders are recognized and granted access.
socket.on("device.request.allowed", (event) => {
console.log(event)
})
You can now begin listening for events from the access control system. The following example listens for device.request.allowed
events, which are emitted when holders are recognized and granted access.
socket.on("message", async (event) => {
event = event.toString("utf8") // Convert the buffer to a string
event = event.replace(/^\d*/, "") // Remove packet type integers
if (event) {
const parts = JSON.parse(event)
const topic = parts[0]
const body = parts[1]
if (topic == "device.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 device.request.allowed
and the body includes details about the device, holder, and credential.
{
"id": "c9daad36-df91-447e-b199-363e425c3084",
"topic": "device.request.allowed",
"body": {
"deviceId": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"holderId": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"credentialId": "56bed685-aa32-4ae2-a9da-323cdd38ebc0",
"requestFactor": "CARD_ONLY",
"facilityCode": "77",
"cardNumber": "26858",
"device": {
"id": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"name": "Test Device",
"connection": "5b34aff3-e883-4c32-88e1-b6d36fbce93a",
"port": 1
},
"holder": {
"id": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"name": "John Wiegand",
"enabled": true,
"activeDate": null,
"expireDate": null
}
},
"cloudNode": {
"id": "ad69a1c0-1390-4ddd-9d49-01c388c3744b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1719356407640
}
Renewing the system token
- socket.io-client
- ws
When an event occurs after a system token has expired, an invalidToken
event will be emitted in its place. You can renew system tokens automatically by listening for invalidToken
events and emitting a renewedToken
event in response. Once the 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 systemToken = await getSystemToken()
socket.emit("renewedToken", { token: systemToken })
})
When an event occurs after a system token has expired, an invalidToken
event will be emitted in its place. You can renew system tokens automatically by listening for invalidToken
events and emitting a renewedToken
event in response. Once the 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.toString("utf8") // Convert the buffer to a string
event = event.replace(/^\d*/, "") // Remove packet type integers
if (event) {
const parts = JSON.parse(event)
const topic = parts[0]
const body = parts[1]
if (topic == "invalidToken") {
const packetType = 42 // Packet type code for message events
const systemToken = await getSystemToken()
socket.send(packetType + JSON.stringify(["renewedToken", { token: systemToken }]))
}
}
})
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.
device.alarm.forced
This event is emitted when a device equipped with a door position sensor (DPS) is forced open.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.alarm.forced",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.alarm.forced.cleared
This event is emitted when a forced alarm has cleared.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.alarm.forced.cleared",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.alarm.propped.off
This event is emitted when a prop alarm has cleared for a device.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.alarm.propped.off",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.alarm.propped.on
This event is emitted when a device equipped with a door position sensor (DPS) is propped open.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.alarm.propped.on",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.autoopen.off
This event is emitted when auto open is deactivated.
{
"id": "05158b99-82c0-47f9-96db-9c715625d431",
"topic": "device.autoopen.off",
"body": {
"deviceId": "6e3948a3-63c8-441b-a2d3-13039bdc5843",
"device": {
"id": "6e3948a3-63c8-441b-a2d3-13039bdc5843",
"name": "Test Device",
"connection": "3a897840-33a7-4e6d-841d-a35e2906960b",
"port": 1
}
},
"cloudNode": {
"id": "b1add759-dfce-4473-9116-8ca55a59cb77",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1712593109999
}
device.autoopen.on
This event is emitted when auto open is activated.
{
"id": "9e6bdc19-5cda-4f5a-aee8-99ee0b58f6f8",
"topic": "device.autoopen.on",
"body": {
"deviceId": "6e3948a3-63c8-441b-a2d3-13039bdc5843",
"device": {
"id": "6e3948a3-63c8-441b-a2d3-13039bdc5843",
"name": "Test Device",
"connection": "3a897840-33a7-4e6d-841d-a35e2906960b",
"port": 1
}
},
"cloudNode": {
"id": "b1add759-dfce-4473-9116-8ca55a59cb77",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1712593109999
}
device.forceclose.off
This event is emitted when do not disturb (DND) is deactivated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.forceclose.off",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.forceclose.on
This event is emitted when do not disturb (DND) is activated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.forceclose.on",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.forceopen.off
This event is emitted when force unlock is deactivated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.forceopen.off",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.forceopen.on
This event is emitted when force unlock is activated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.forceopen.on",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.dps.closed
This event is emitted when a door position sensor (DPS) changes to a closed state.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.dps.closed",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.dps.opened
This event is emitted when a door position sensor (DPS) changes to an open state.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.dps.opened",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.relay.off
This event is emitted when a device is locked.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.relay.off",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.relay.on
This event is emitted when a device is unlocked.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.relay.on",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.rex.off
This event is emitted when the request to exit (REX) is deactivated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.rex.off",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.input.rex.on
This event is emitted when the request to exit (REX) is activated.
{
"id": "eb3accb3-f66c-4d62-aa68-2291bdd77b39",
"topic": "device.input.rex.on",
"body": {
"deviceId": "27e9674c-dd41-4404-b030-cc697c6f599d",
"device": {
"id": "09098929-9415-442f-b108-4093b39b25d2",
"name": "Test Device",
"connection": "ce7ae800-7327-45f7-ad70-4d4a8d6347f6",
"port": 1
}
},
"cloudNode": {
"id": "2c1ec505-6ed1-4bf4-b82a-6bc8fcf6267b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1712593100540
}
device.request.allowed
This event is emitted when a holder is recognized and granted access.
{
"id": "c9daad36-df91-447e-b199-363e425c3084",
"topic": "device.request.allowed",
"body": {
"deviceId": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"holderId": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"credentialId": "56bed685-aa32-4ae2-a9da-323cdd38ebc0",
"requestFactor": "CARD_ONLY",
"facilityCode": "77",
"cardNumber": "26858",
"device": {
"id": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"name": "Test Device",
"connection": "5b34aff3-e883-4c32-88e1-b6d36fbce93a",
"port": 1
},
"holder": {
"id": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"name": "John Wiegand",
"enabled": true,
"activeDate": null,
"expireDate": null
}
},
"cloudNode": {
"id": "ad69a1c0-1390-4ddd-9d49-01c388c3744b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1719356407640
}
device.request.denied
This event is emitted when a holder is recognized but denied access.
{
"id": "c9daad36-df91-447e-b199-363e425c3084",
"topic": "device.request.denied",
"body": {
"deviceId": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"holderId": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"credentialId": "56bed685-aa32-4ae2-a9da-323cdd38ebc0",
"requestFactor": "CARD_ONLY",
"facilityCode": "77",
"cardNumber": "26858",
"device": {
"id": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"name": "Test Device",
"connection": "5b34aff3-e883-4c32-88e1-b6d36fbce93a",
"port": 1
},
"holder": {
"id": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"name": "John Wiegand",
"enabled": true,
"activeDate": null,
"expireDate": null
}
},
"cloudNode": {
"id": "ad69a1c0-1390-4ddd-9d49-01c388c3744b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1719356407640
}
device.request.unknown
This event is emitted when a credential is presented but the holder is not recognized.
{
"id": "c9daad36-df91-447e-b199-363e425c3084",
"topic": "device.request.unknown",
"body": {
"deviceId": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"facilityCode": "77",
"cardNumber": "26858",
"device": {
"id": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"name": "Test Device",
"connection": "5b34aff3-e883-4c32-88e1-b6d36fbce93a",
"port": 1
}
},
"cloudNode": {
"id": "ad69a1c0-1390-4ddd-9d49-01c388c3744b",
"serialNumber": "1234ABC",
"name": "Test Cloud Node"
},
"occurred": 1719356407640
}
endpoint.sync.finished
This event is emitted when a controller has finished syncing.
{
"id": "0942d921-09ff-48e2-9092-cb3de0a96c42",
"topic": "endpoint.sync.finished",
"body": {
"connectionId": "3a897840-33a7-4e6d-841d-a35e2906960b",
"address": "",
"type": "ao_rules"
},
"cloudNode": {
"id": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1712593109999
}
endpoint.sync.finished.all
This event is emitted when all controllers connected to a cloud node have finished syncing.
{
"id": "0942d921-09ff-48e2-9092-cb3de0a96c42",
"topic": "endpoint.sync.finished.all",
"body": {
"connectionId": "3a897840-33a7-4e6d-841d-a35e2906960b",
"address": "",
"type": "ao_rules"
},
"cloudNode": {
"id": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1712593109999
}
endpoint.sync.started
This event is emitted when a controller has started syncing.
{
"id": "0942d921-09ff-48e2-9092-cb3de0a96c42",
"topic": "endpoint.sync.started",
"body": {
"connectionId": "3a897840-33a7-4e6d-841d-a35e2906960b",
"address": "",
"type": "ao_rules"
},
"cloudNode": {
"id": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1712593109999
}
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.
{
"topic": "device.request.allowed",
"info": "Person \"%s\" has requested access to the device \"%s\". Access allowed. Person used card for requesting access.",
"placeholders": [
{
"text": "John Wiegand",
"id": "ceee92e3-fdf4-44e6-800a-2d9d51d05583",
"type": "holder"
},
{
"text": "Test Device",
"id": "1b99bf42-1f9c-4184-9b5a-f7be9b24f4be",
"type": "device"
}
],
"cloudNode": {
"id": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"name": "Test Cloud Node",
"serialNumber": "1234ABC"
},
"occurred": 1719358795980
}
sync.not_synced
This event is emitted when a cloud node needs to be synced.
{
"systemId": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"panelSn": "1234ABC",
"details": {
"dataFlowStatus": "Pending",
"timestamp": 1712591860790
}
}
sync.synced
This event is emitted when a cloud node has finished syncing.
{
"systemId": "19a808d6-0e6a-4202-9dde-64a20ec3ad09",
"panelSn": "1234ABC",
"details": {
"timestamp": 1712591860790
}
}