Event Catalog
Every webhook delivery contains a single event object. This page documents the event envelope structure and the payload for each event type.
Event Envelope
All events share the same top-level structure:
{
"id": "evt_abc123",
"type": "asset.created",
"organizationId": "o-xxxx",
"created": 1771911526,
"data": {
"object": { },
"previousAttributes": { }
},
"livemode": true
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event identifier. Use this for idempotency. Format: evt_ prefix + random string. |
type | string | The event type (e.g., asset.created). Determines the shape of data.object. |
organizationId | string | The organization where the event occurred. |
created | integer | Unix timestamp (seconds) when the event was generated. |
data.object | object | The full resource at the time of the event. |
data.previousAttributes | object | For .updated and .status.changed events only. Contains the fields that changed, with their previous values. |
isTest | boolean | Present and true only on test events (webhook.test). Absent on real events. |
livemode | boolean | true for real events, false for test events. |
The previousAttributes field makes it easy to detect exactly what changed without diffing the entire object. For .created events, this field is absent.
Asset Events
asset.created
Fired when a new asset is added to the organization.
{
"id": "evt_ast_001",
"type": "asset.created",
"organizationId": "o-xxxx",
"created": 1771911526,
"data": {
"object": {
"id": "ast-a1b2c3d4",
"name": "HVAC Unit #12",
"assetTypeId": "at-cooling",
"locationId": "loc-building-a",
"status": "Online",
"metadata": {
"manufacturer": "Daikin",
"model": "VRV-IV",
"serialNumber": "DK-2024-00412"
},
"created": 1771911526,
"updated": 1771911526
}
},
"livemode": true
}
asset.telemetry.updated
Fired when an IoT sensor sends new telemetry data for an asset (e.g., temperature, humidity, CO2 level). This is the primary event for real-time monitoring and digital twin integrations.
This event fires every time a sensor reading updates the asset record. For assets with sensors reporting every 30 seconds, expect one event per interval. Only subscribe if your integration needs real-time sensor data.
{
"id": "evt_ast_002",
"type": "asset.telemetry.updated",
"organizationId": "o-xxxx",
"created": 1771912000,
"data": {
"object": {
"id": "ast-a1b2c3d4",
"name": "IAQ Sensor - Level 3 Lobby",
"assetTypeId": "at-iaq",
"locationId": "loc-building-a",
"status": "Online",
"connectionStatus": "connected",
"isOnline": true,
"lastTelemetry": {
"temperature": 24.5,
"humidity": 65.2,
"co2": 412,
"pm25": 8.3
},
"lastSeen": 1771912000
},
"previousAttributes": {
"lastTelemetry": {
"temperature": 23.8,
"humidity": 64.1,
"co2": 408,
"pm25": 8.1
},
"lastSeen": 1771911970
}
},
"livemode": true
}
Understanding telemetry fields
The lastTelemetry object contains decoded sensor values — the fields and their names depend on the sensor type (asset type). For example:
| Sensor Type | Example Fields |
|---|---|
| IAQ (Indoor Air Quality) | Temperature, Humidity, CO2, PM2Point5, PM10, TVOC, HCHO, Pressure |
| Water Leakage | LeakStatus, BatteryLevel |
| Temperature & Humidity | Temperature, Humidity |
| Power Socket | Power, Voltage, Current, Energy |
| Door/Window Sensor | DoorStatus, BatteryLevel |
Getting units and labels
Telemetry values are raw numbers (e.g., "CO2": 1761). Units (ppm, °C, %RH) are defined on the asset type, not on each telemetry reading. To get units for display:
# Fetch the asset type definition (cache this — it rarely changes)
curl https://app.infodeck.io/api/v2/organizations/{orgId}/asset-types/{assetTypeId} \
-H "Authorization: Bearer {token}"
The response includes a properties array with unit metadata:
{
"properties": [
{ "key": "Temperature", "name": "Temperature", "type": "float", "unit": "°C" },
{ "key": "Humidity", "name": "Humidity", "type": "float", "unit": "%RH" },
{ "key": "CO2", "name": "CO2", "type": "integer", "unit": "ppm" },
{ "key": "PM2Point5", "name": "PM2.5", "type": "float", "unit": "µg/m³" }
]
}
Fetch the asset type definition once when you first see an assetTypeId, cache it, and use it to map units to all future telemetry values. The assetTypeId is included in every asset.telemetry.updated event for this purpose.
asset.deleted
Fired when an asset is removed.
{
"id": "evt_ast_003",
"type": "asset.deleted",
"organizationId": "o-xxxx",
"created": 1771913000,
"data": {
"object": {
"id": "ast-a1b2c3d4",
"deleted": true
}
},
"livemode": true
}
asset.status.changed
Fired when an asset's connectivity or operational status changes. The connectionStatus field is a boolean (true = online, false = offline) computed from the device's last uplink timestamp and its keepAlive interval. Infodeck checks connectivity every 2 minutes.
{
"id": "evt_abc123",
"type": "asset.status.changed",
"organizationId": "o-xxxx",
"streamRecordId": "rec_abc123",
"created": 1771914000,
"data": {
"object": {
"id": "ast-a1b2c3d4",
"name": "HVAC Unit #12 (Lobby)",
"organizationId": "o-xxxx",
"assetTypeId": "at-HmlE8PRGLx",
"locationId": "l-sv09bQPYEK",
"status": "Normal",
"connectionStatus": false,
"isOnline": null
},
"previousAttributes": {
"connectionStatus": true
}
},
"livemode": true
}
Status fields explained
| Field | Type | Description |
|---|---|---|
connectionStatus | boolean | true = device sent data within its keepAlive window. false = device is offline (no uplink within keepAlive). |
status | string | Operational status: "Normal", "Warning", or "Critical". Set by alert rules, not connectivity. |
isOnline | boolean \| null | Legacy field. Use connectionStatus instead. |
Check previousAttributes.connectionStatus to determine the direction of change:
previousAttributes.connectionStatus: true→ device just went offlinepreviousAttributes.connectionStatus: false→ device just came back onlinepreviousAttributes: {}(empty) → first time connectivity was recorded for this device
How connectivity detection works
Infodeck checks device connectivity every 2 minutes using this formula:
isConnected = lastUplinkTimestamp + (keepAlive × 1000) > now
keepAliveis configurable per asset (default: 86,400 seconds = 24 hours). For example, an indoor air quality sensor sending data every 30 minutes might usekeepAlive: 1800.- When a device stops sending data and the keepAlive window expires, the next connectivity check writes
connectionStatus: falseand fires this webhook. - When the device resumes sending data, the next connectivity check writes
connectionStatus: trueand fires again.
Example: full online → offline → online cycle
Step 1 — Device is online, sending telemetry normally. No asset.status.changed fires (status unchanged).
Step 2 — Device goes offline (unplugged, out of range, or powered off). After the keepAlive window expires (e.g., 30 minutes for keepAlive: 1800), the next 2-minute connectivity check detects the device is offline:
{
"type": "asset.status.changed",
"data": {
"object": {
"connectionStatus": false
},
"previousAttributes": {
"connectionStatus": true
}
}
}
Step 3 — Device comes back online and sends telemetry. The next 2-minute connectivity check detects the device is online again:
{
"type": "asset.status.changed",
"data": {
"object": {
"connectionStatus": true
},
"previousAttributes": {
"connectionStatus": false
}
}
}
The worst-case delay for detecting a status change is keepAlive + 2 minutes (keepAlive window expiry + next checker cycle). For a sensor with keepAlive: 1800 (30 min), expect the offline webhook within ~32 minutes of the last uplink. For reconnection, the webhook fires within 2 minutes of the first new uplink.
Work Order Events
workorder.created
Fired when a new work order is opened.
{
"id": "evt_wo_001",
"type": "workorder.created",
"organizationId": "o-xxxx",
"created": 1771920000,
"data": {
"object": {
"id": "wo-x1y2z3",
"title": "Fix leaking pipe in B2 washroom",
"description": "Water leaking from ceiling pipe near sink area",
"status": "Open",
"priority": "High",
"locationId": "loc-building-b",
"assigneeId": "usr-tech-01",
"reportedBy": "usr-fm-manager",
"slaResponseDeadline": 1771927200,
"slaResolutionDeadline": 1771963200,
"created": 1771920000,
"updated": 1771920000
}
},
"livemode": true
}
workorder.updated
Fired when a work order's details are modified (title, description, priority, assignee, etc.).
{
"id": "evt_wo_002",
"type": "workorder.updated",
"organizationId": "o-xxxx",
"created": 1771921000,
"data": {
"object": {
"id": "wo-x1y2z3",
"title": "Fix leaking pipe in B2 washroom",
"status": "Open",
"priority": "Critical",
"locationId": "loc-building-b",
"assigneeId": "usr-tech-02",
"created": 1771920000,
"updated": 1771921000
},
"previousAttributes": {
"priority": "High",
"assigneeId": "usr-tech-01"
}
},
"livemode": true
}
workorder.status.changed
Fired when a work order transitions to a new status.
{
"id": "evt_wo_003",
"type": "workorder.status.changed",
"organizationId": "o-xxxx",
"created": 1771925000,
"data": {
"object": {
"id": "wo-x1y2z3",
"title": "Fix leaking pipe in B2 washroom",
"status": "InProgress",
"priority": "Critical",
"created": 1771920000,
"updated": 1771925000
},
"previousAttributes": {
"status": "Open"
}
},
"livemode": true
}
workorder.completed
Fired when a work order is marked as completed.
{
"id": "evt_wo_004",
"type": "workorder.completed",
"organizationId": "o-xxxx",
"created": 1771940000,
"data": {
"object": {
"id": "wo-x1y2z3",
"title": "Fix leaking pipe in B2 washroom",
"status": "Completed",
"priority": "Critical",
"completedAt": 1771940000,
"completedBy": "usr-tech-02",
"created": 1771920000,
"updated": 1771940000
},
"previousAttributes": {
"status": "InProgress"
}
},
"livemode": true
}
Booking Events
booking.created
Fired when a new booking is made.
{
"id": "evt_bk_001",
"type": "booking.created",
"organizationId": "o-xxxx",
"created": 1771950000,
"data": {
"object": {
"id": "bk-m1n2o3",
"resourceId": "res-meeting-room-5a",
"resourceName": "Meeting Room 5A",
"locationId": "loc-building-a",
"bookedBy": "usr-john",
"startTime": 1771963200,
"endTime": 1771966800,
"status": "Confirmed",
"created": 1771950000
}
},
"livemode": true
}
booking.cancelled
Fired when a booking is cancelled.
{
"id": "evt_bk_002",
"type": "booking.cancelled",
"organizationId": "o-xxxx",
"created": 1771955000,
"data": {
"object": {
"id": "bk-m1n2o3",
"resourceId": "res-meeting-room-5a",
"status": "Cancelled",
"cancelledBy": "usr-john",
"cancelledAt": 1771955000,
"created": 1771950000
},
"previousAttributes": {
"status": "Confirmed"
}
},
"livemode": true
}
booking.checked_in
Fired when a guest checks in for their booking.
{
"id": "evt_bk_003",
"type": "booking.checked_in",
"organizationId": "o-xxxx",
"created": 1771963500,
"data": {
"object": {
"id": "bk-m1n2o3",
"resourceId": "res-meeting-room-5a",
"status": "CheckedIn",
"checkedInAt": 1771963500,
"created": 1771950000
},
"previousAttributes": {
"status": "Confirmed"
}
},
"livemode": true
}
Visitor Events
visitor.registered
Fired when a new visitor is pre-registered.
{
"id": "evt_vis_001",
"type": "visitor.registered",
"organizationId": "o-xxxx",
"created": 1771970000,
"data": {
"object": {
"id": "vis-p1q2r3",
"name": "Jane Smith",
"email": "jane.smith@example.com",
"company": "Acme Corp",
"hostId": "usr-john",
"locationId": "loc-building-a",
"expectedArrival": 1771984800,
"status": "Registered",
"created": 1771970000
}
},
"livemode": true
}
visitor.checked_in
Fired when a visitor checks in at the location.
{
"id": "evt_vis_002",
"type": "visitor.checked_in",
"organizationId": "o-xxxx",
"created": 1771984900,
"data": {
"object": {
"id": "vis-p1q2r3",
"name": "Jane Smith",
"status": "CheckedIn",
"checkedInAt": 1771984900,
"created": 1771970000
},
"previousAttributes": {
"status": "Registered"
}
},
"livemode": true
}
visitor.checked_out
Fired when a visitor checks out.
{
"id": "evt_vis_003",
"type": "visitor.checked_out",
"organizationId": "o-xxxx",
"created": 1771999200,
"data": {
"object": {
"id": "vis-p1q2r3",
"name": "Jane Smith",
"status": "CheckedOut",
"checkedOutAt": 1771999200,
"created": 1771970000
},
"previousAttributes": {
"status": "CheckedIn"
}
},
"livemode": true
}
SOR Events (Schedule of Rates)
sor.schedule.published
Fired when a SOR schedule is published and made active.
{
"id": "evt_sor_001",
"type": "sor.schedule.published",
"organizationId": "o-xxxx",
"created": 1772000000,
"data": {
"object": {
"id": "sor-sch-a1b2c3",
"name": "FY2026 Maintenance Rates",
"status": "Published",
"effectiveFrom": 1772000000,
"effectiveTo": 1803536000,
"itemCount": 245,
"publishedBy": "usr-fm-manager",
"publishedAt": 1772000000,
"created": 1771900000
},
"previousAttributes": {
"status": "Draft"
}
},
"livemode": true
}
sor.quotation.submitted
Fired when a SOR quotation is submitted for approval.
{
"id": "evt_sor_002",
"type": "sor.quotation.submitted",
"organizationId": "o-xxxx",
"created": 1772010000,
"data": {
"object": {
"id": "sor-qt-d4e5f6",
"scheduleId": "sor-sch-a1b2c3",
"workOrderId": "wo-x1y2z3",
"status": "Submitted",
"totalAmountCents": 1250000,
"lineItemCount": 8,
"submittedBy": "usr-contractor-01",
"submittedAt": 1772010000,
"created": 1772005000
},
"previousAttributes": {
"status": "Draft"
}
},
"livemode": true
}
Money values are always in integer cents. 1250000 = $12,500.00.
sor.quotation.approved
Fired when a SOR quotation is approved.
{
"id": "evt_sor_003",
"type": "sor.quotation.approved",
"organizationId": "o-xxxx",
"created": 1772020000,
"data": {
"object": {
"id": "sor-qt-d4e5f6",
"scheduleId": "sor-sch-a1b2c3",
"workOrderId": "wo-x1y2z3",
"status": "Approved",
"totalAmountCents": 1250000,
"approvedBy": "usr-fm-manager",
"approvedAt": 1772020000,
"created": 1772005000
},
"previousAttributes": {
"status": "Submitted"
}
},
"livemode": true
}
Test Events
webhook.test
Sent when you click "Send Test" in the dashboard or call the test endpoint. Use this to verify your endpoint is reachable and your signature verification works.
{
"id": "evt_test_xyz789",
"type": "webhook.test",
"organizationId": "o-xxxx",
"created": 1771911600,
"data": {
"object": {
"message": "This is a test webhook event from Infodeck"
}
},
"isTest": true,
"livemode": false
}
Test events have livemode: false and isTest: true. Your handler should process them normally but avoid triggering side effects (e.g., do not create real records from test events).