The UltronSMART Cloud API - Personal supports webhook notifications for device state changes. When a device's state changes, the system automatically sends HTTP POST requests to your configured webhook endpoint.
Last Updated: 2025-11-07
Prerequisites
1. Valid App Subscription
- Must have an active UltronSMART application subscription
- Subscription status must be
active - You can check the available app subscriptions in: https://account.ultroncloud.com/account/app
- Ensure subscription includes webhook functionality permissions
2. Device Assignment to App
- Devices must be added to
appDevicesto receive state change notifications - Only devices assigned to your app will trigger webhook events
- Use
/usr/v5/AddAppDevicesAPI to add devices to your app
3. API Authentication
X-Api-Key: YOUR_API_KEY
Ultron-Cloud-Appid: YOUR_APP_IDSetup Process
Step 1: Add Devices to App
First, ensure the devices you want to monitor are added to your app:
Example: Adding devices to your app
curl -X POST "https://api.ultroncloud.com/usr/v5/AddAppDevices" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Ultron-Cloud-Appid: YOUR_APP_ID" \
-H "Content-Type: application/json" \
-d '{
"appId": "YOUR_APP_ID",
"deviceNameMap": {
"UT3702-ES12345": "Living Room Light",
"UTU301-2K26DVZ": "Temperature Sensor"
}
}'Step 2: Create App Settings for Webhook
Example: Creating a webhook subscription
curl --request POST \
--url https://api.ultroncloud.com/usr/v5/CreateAppWebhook \
--header 'Ultron-Cloud-Appid: YOUR_APP_ID' \
--header 'X-Api-Key: YOUR_API_KEY' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"eventTypes": [
"stateChanged",
" onlineStateChanged",
" deviceListChanged"
],
"appId": "AppID",
"name": "Dev test webhook",
"webhookURL": "https://abc.com/webhook",
"webhookSecret": "WEBHOOK_SECRET"
}
'Step 3: Test Your Webhook URL
Before or after creating your app settings, you can send a test event to your endpoint to verify that it is reachable and can correctly handle signature verification. This API allows you to test any URL without saving it as a setting.
Example: Sending a test event
curl -X POST "https://api.ultroncloud.com/usr/v5/TestWebhookURL" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Ultron-Cloud-Appid: YOUR_APP_ID" \
-H "Content-Type: application/json" \
-d '{
"appId": "YOUR_APP_ID",
"webhookURL": "https://your-domain.com/webhook/test-endpoint",
"webhookSecret": "your-secret-key-for-signature-verification"
}'The system will send a sample test event to the webhookURL provided in the request body. This allows you to validate your endpoint logic before committing to a setting.
Step 4: Configure Webhook Settings (Optional)
Update existing webhook settings if needed:
Example: Updating webhook settings
curl --request POST \
--url https://api.ultroncloud.com/usr/v5/UpdateAppWebhook \
--header 'Ultron-Cloud-Appid: YOUR_APP_ID' \
--header 'X-Api-Key: YOUR_API_KEY' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"eventTypes": [
"stateChanged",
"deviceListChanged",
"onlineStateChanged"
],
"appId": "YOUR_APP_ID",
"name": "NEW Webhook name",
"webhookId": "WEBHOOK_ID",
"webhookSecret": "NEW_WEBHOOK_SECRET",
"webhookURL": "https://abc.com/NEW_WEBHOOK_ENDPOINT"
}
'Step 5: Verify Configuration
Check your current app settings:
Example: Retrieving app settings
curl --request POST \
--url https://api.ultroncloud.com/usr/v5/ListAppWebhooks \
--header 'Ultron-Cloud-Appid: YOUR_APP_ID' \
--header 'X-Api-Key: YOUR_API_KEY' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"appId": "YOUR_APP_ID"
}
'Verify devices are added to your app:
Example: Retrieving app devices
curl -X POST "https://api.ultroncloud.com/usr/v5/GetUserAppDevices" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Ultron-Cloud-Appid: YOUR_APP_ID" \
-H "Content-Type: application/json" \
-d '{
"appId": "YOUR_APP_ID"
}'Webhook Event Format
Event Types
stateChanged: A device's state has changed.onlineStateChanged: A device's online/offline status has changed.deviceListChanged: A device has been added to or removed from a group.
Note: This list may be expanded in the future. Refer to the latest API documentation for the most up-to-date list of event types.
Understanding Event Scopes
It's important to understand that not all event types are triggered under the same conditions. They are designed for different purposes: discovery and monitoring.
Discovery Event: deviceListChanged
deviceListChangedThe deviceListChanged event operates at a higher level. Its primary purpose is to notify your application of potential devices it might be interested in.
- Trigger: This event fires whenever a device is added to or removed from a user's group.
- Action: Upon receiving this event, your application can inspect the
newDeviceslist and decide if it wants to start monitoring any of them. To begin receiving detailed state updates for a new device, you must explicitly add it to your app's scope using the/usr/v5/AddAppDevicesendpoint.
Monitoring Events: stateChanged & onlineStateChanged
stateChanged & onlineStateChangedThese events are more precise and are designed for monitoring devices that your application is actively managing.
- Trigger: These events will only fire for devices that you have explicitly added to your app using the
/usr/v5/AddAppDevicesendpoint. - Action: Use these webhooks to keep your application's state in sync with the device's real-time status and data. If a device has not been added to your app, you will not receive its
stateChangedoronlineStateChangednotifications.
Request Body
stateChanged
{
"eventType": "stateChanged",
"data": {
"groupId": "EoiXXXXXXXXEZpfYVC9q4fC",
"uid": "6KeZXXXXXXeNfbXFmhXXXXXX2",
"sn": "UTU309-F8AEXXXX643",
"publishTime": "2025-04-08T07:52:27.556299908Z",
"statesData": {
"states": {
"pir_sensor": {
"serialNo": 83,
"detected": ["motionDetected"]
}
},
"reqId": null,
"devices": ["pir_sensor"]
}
},
"timestamp": 1744098747
}onlineStateChanged
{
"eventType": "onlineStateChanged",
"data": {
"groupId": "EoiXXXXXXXXEZpfYVC9q4fC",
"uid": "6KeZXXXXXXeNfbXFmhXXXXXX2",
"sn": "UTU309-F8AEXXXX643",
"publishTime": "2025-04-08T08:00:00Z",
"onlineState": {
"online": true,
"time": "2025-04-08T08:00:00Z"
}
},
"timestamp": 1744099200
}deviceListChanged
{
"eventType": "deviceListChanged",
"data": {
"groupId": "EoiXXXXXXXXEZpfYVC9q4fC",
"newDevices": ["UT3702-ES12345"],
"removedDevices": [],
"publishTime": "2025-04-08T09:00:00Z"
},
"timestamp": 1744102800
}Field Descriptions
eventType: (string) The type of event. Can bestateChanged,onlineStateChanged, ordeviceListChanged.data: (object) An object containing the details of the event.groupId: (string) The ID of the group the device belongs to. For organization-level webhooks, this field will be omitted.collectionId: (string) (For organization webhooks only) The ID of the collection the device belongs to.uid: (string) The unique ID of the user associated with the device. (Not present indeviceListChanged)sn: (string) The product serial number (ProductSN) of the device. (Not present indeviceListChanged)publishTime: (string) The timestamp when the event was published, in RFC3339 format.statesData: (object) (ForstateChangedonly) An object containing the state data.onlineState: (object) (ForonlineStateChangedonly) An object containing the online state.newDevices: (array of string) (FordeviceListChangedonly) A list of ProductSNs for devices added to the group.removedDevices: (array of string) (FordeviceListChangedonly) A list of ProductSNs for devices removed from the group.
timestamp: (integer) The Unix timestamp when the event occurred.
Request Headers
Content-Type: application/json
X-Ultron-Signature: <base64-encoded-hmac-sha256-signature>Request Body
{
"eventType": "stateChanged",
"data": {
"groupId": "EoiXXXXXXXXEZpfYVC9q4fC",
"uid": "6KeZXXXXXXeNfbXFmhXXXXXX2",
"sn": "UTU309-F8AEXXXX643",
"publishTime": "2025-04-08T07:52:27.556299908Z",
"statesData": {
"states": {
"pir_sensor": {
"serialNo": 83,
"detected": ["motionDetected"]
}
},
"reqId": null,
"devices": ["pir_sensor"]
}
},
"timestamp": 1744098747
}Field Descriptions
eventType: (string) The type of event. In this case, it's always"stateChanged".data: (object) An object containing the details of the state change.groupId: (string) The ID of the group the device belongs to.uid: (string) The unique ID of the user associated with the device.sn: (string) The product serial number (ProductSN) of the device.publishTime: (string) The timestamp when the state change was published, in RFC3339 format.statesData: (object) An object containing the state data.states: (object) A map where the key is the device name, and the value is an object containing the device's state.reqId: (null) The request ID.devices: (array of string) An array of device names.
timestamp: (integer) The Unix timestamp when the event occurred.
Webhook Event Security: Signature Verification
It is strongly recommended to verify the signature of each incoming webhook event. This ensures that the event originated from the Ultron Cloud platform, and not from a malicious source.
Signature Details
- Header: The signature is provided in the
X-Ultron-Signatureheader of the HTTP request. - Algorithm: The signature is generated using the HMAC-SHA256 algorithm.
- Secret: The secret key used for signature generation is obtained from the
GetAppSettingsAPI. The secret is returned in thewebhookSecretfield when you create the app settings.
Hash Calculation Algorithm
secretBytes = []byte(secret): Convert the secret string to a byte array.hash = HMAC-SHA256(requestBody, secretBytes): Calculate the HMAC-SHA256 hash of the request body using the secret key.requestBody: The raw byte array of the HTTP request body.secretBytes: The secret key as a byte array.
X-Ultron-Signature = base64StdEncode(hash): Base64 encode the resulting hash. The result is the value of theX-Ultron-Signatureheader.
Signature Verification Example
Given:
webhookSecret: "JpLvyZUcvFaXXXXXXXsqniG"requestBodyStr:{"eventType":"stateChanged","data":{"groupId":"EoiXXXXXXXXEZpfYVC9q4fC","uid":"6KeZXXXXXXeNfbXFmhXXXXXX2","sn":"UTU309-F8AEXXXX643","publishTime":"2025-04-08T07:52:27.556299908Z","statesData":{"states":{"pir_sensor":{"serialNo":83,"detected":["motionDetected"]}},"reqId":null,"devices":["pir_sensor"]}},"timestamp":1744098747}
Calculation Steps:
-
Convert Secret to Byte Array (
secretBytes):[74 112 76 118 121 90 85 99 118 70 97 88 88 88 88 88 88 88 115 113 110 105 71] -
Calculate HMAC-SHA256 Hash (
hash):[34 237 114 244 228 117 28 190 87 82 133 179 140 30 8 27 90 159 252 164 6 199 198 145 16 181 13 27 151 31 89 135] -
Base64 Encode the Hash (
signature):Iu1y9OR1HL5XUoWzjB4IG1qf/KQGx8aRELUNG5cfWYc=
Implementation Examples
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, secret, signature) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('base64');
return signature === expectedSignature;
}
// Usage
const receivedSignature = req.headers['x-ultron-signature'];
const payload = JSON.stringify(req.body);
const isValid = verifyWebhookSignature(
payload,
'your-webhook-secret',
receivedSignature
);Python
import hmac
import hashlib
import base64
import json
def verify_webhook_signature(payload, secret, signature):
expected_signature = base64.b64encode(
hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).digest()
).decode('utf-8')
return signature == expected_signature
# Usage
received_signature = request.headers.get('X-Ultron-Signature')
payload = json.dumps(request.json, separators=(',', ':'))
is_valid = verify_webhook_signature(
payload,
'your-webhook-secret',
received_signature
)Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
)
func CalculateWebhookSignature(message []byte, secret string) string {
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write(message)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func VerifyWebhookSignature(payload []byte, secret, signature string) bool {
expectedSignature := CalculateWebhookSignature(payload, secret)
return signature == expectedSignature
}Error Handling
Common Error Codes
311:SkuOutOfSubscriptionItem- Subscription doesn't include webhook functionality323:SkuSubscriptionInactive- Subscription is inactive105:CmdInvalidParams- Invalid webhook URL format100:CmdDeviceNotFound- Device not found in app devices
Webhook Delivery
- Timeout: 30 seconds
- Retry Policy: 3 attempts with exponential backoff
- Expected Response: HTTP 200-299 status codes
- Response Time: Should respond within 5 seconds
Best Practices
Security
- Always verify signatures using the webhook secret
- Use HTTPS for webhook endpoints
- Validate payload structure before processing
- Rate limiting to prevent abuse
Reliability
- Idempotency: Handle duplicate events gracefully
- Quick Response: Return HTTP 200 within 5 seconds
- Async Processing: Process webhook data asynchronously
- Error Logging: Log failed webhook deliveries for debugging
Example Webhook Handler
app.post('/webhook/device-state', (req, res) => {
try {
// 1. Verify signature
const signature = req.headers['x-ultron-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, webhookSecret, signature)) {
return res.status(401).send('Invalid signature');
}
// 2. Quick response
res.status(200).send('OK');
// 3. Process asynchronously
processDeviceStateChange(req.body);
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('Internal server error');
}
});Next Steps
After completing these prerequisites, you can proceed with the following:
- Create a Webhook Endpoint: Set up a publicly accessible URL to receive webhook events.
- Configure Webhooks: Use the
CreateAppSettingsAPI to configure your webhook URL and the events you want to receive. - Handle Webhook Events: Implement logic in your application to receive, verify, and process incoming webhook events.
Troubleshooting
Webhook Not Receiving Events
- Send a test event using the
/usr/v5/TestWebhookURLAPI to your endpoint. This is the best way to confirm basic connectivity and signature handling without involving actual device events. - Check if devices are added to app using
/usr/v5/GetUserAppDevices - Verify webhook URL is accessible and returns 200
- Ensure subscription includes webhook permissions
- Check webhook settings are configured using
/usr/v5/GetAppSettings
Signature Verification Fails
- Ensure webhook secret matches the configured secret
- Verify payload is not modified before verification
- Check signature calculation algorithm implementation
- Ensure proper encoding (UTF-8 for payload, Base64 for signature)
- Make sure JSON payload format matches exactly (no extra spaces)