Overview
Azure IoT Hub is Microsoft's cloud-scale message broker for IoT devices. UniAsset's IoT Signal ingestion endpoint accepts the telemetry that IoT Hub forwards, recording every signal against the asset it belongs to.
This article walks through:
- The prerequisites
- Creating an API key in UniAsset
- Configuring an Azure IoT Hub message route
- The payload format UniAsset expects
- Verifying signals are arriving
- Troubleshooting common errors
Plan required: Enterprise
Note: Phase 1 of the IoT Signal foundation stores and displays signals. It does not yet trigger automation, work orders, or notifications. Those capabilities ship in a later phase on top of the same ingestion contract.
Prerequisites
Before you begin, confirm you have:
- A UniAsset organization on the Enterprise plan
- The Owner role in UniAsset (required to create integration API keys)
- An active Azure subscription with an Azure IoT Hub instance
- Permission in Azure to add a custom endpoint to IoT Hub and to create a message route
- HTTPS connectivity from Azure to
https://<your-uniasset-domain>
Step 1 — Create a UniAsset API key
- Sign in to UniAsset as the Owner.
- Click Settings in the left sidebar.
- Open the Integrations tab.
- Click API Keys → Create API Key.
- Give it a descriptive name (for example,
Azure IoT Hub — Production). - Grant only the
iot:signal:ingestpermission. Avoid adding write permissions that this integration does not need. - Click Generate. The full key is shown once — copy it now and store it in Azure Key Vault. UniAsset only persists a bcrypt hash; the plaintext key cannot be recovered.
The key format is ua_live_<64-hex-chars>. The first 12 characters are an unencrypted prefix used for lookup.
Step 2 — Add UniAsset as a custom endpoint in Azure IoT Hub
In the Azure portal:
- Open your IoT Hub instance.
- Under Hub settings, select Message routing.
- Switch to the Custom endpoints tab and click + Add → Webhook.
- Name the endpoint
UniAsset. - Set the URI to
https://<your-uniasset-domain>/api/integrations/iot/signals. - Under Custom headers, add:
Authorization: Bearer ua_live_<your-key>Content-Type: application/json
- Save the endpoint.
Step 3 — Create the message route
- In Message routing, switch to the Routes tab and click + Add.
- Name the route
forward-to-uniasset. - Data source: Device Telemetry Messages.
- Endpoint: the
UniAssetendpoint you just created. - Routing query: leave as
trueto forward everything, or scope it (for example,signalType = 'temperature.high') if your devices already emit asignalTypeproperty. - Save.
Step 4 — Shape the payload
UniAsset expects each signal as a JSON object with at least source, signalType, and payload. Devices that emit raw sensor messages should transform them either at the edge or via an Azure Function bridge.
{
"assetId": "ckxyz123abc",
"deviceId": "azure-device-001",
"source": "AzureIoTHub",
"signalType": "temperature.high",
"severity": "HIGH",
"numericValue": 92.5,
"payload": {
"temperature": 92.5,
"unit": "C",
"messageId": "12fa..."
}
}
| Field | Required | Notes |
|---|---|---|
source | Yes | Always "AzureIoTHub" for this integration. |
signalType | Yes | Free-form, e.g. temperature.high, vibration.spike. Use a dotted-path convention so future rules can pattern-match. |
payload | Yes | Raw payload object. Stored verbatim — keep the original device fields here. |
assetId | No | UniAsset asset id. If your IoT Hub device twins already store this, include it. |
deviceId | No | Stable Azure device identifier. Useful for reconciliation. |
severity | No | One of INFO, LOW, MEDIUM, HIGH, CRITICAL. Free text allowed but normalized to upper case server-side. |
numericValue / textValue / booleanValue | No | Typed projections for charting and filtering. The raw payload is always the source of truth. |
Edge transformation with an Azure Function
If your devices emit a raw schema, the simplest pattern is an HTTP-triggered Azure Function that receives the IoT Hub message, transforms it, and POSTs to UniAsset. A starting point:
export async function azureIoTBridge(req: { body: any }) {
const raw = req.body;
const uniassetPayload = {
deviceId: raw.systemProperties["iothub-connection-device-id"],
source: "AzureIoTHub",
signalType: raw.properties.signalType ?? "telemetry.raw",
severity: raw.properties.severity,
numericValue: raw.body.value,
payload: raw.body,
};
await fetch("https://<your-uniasset-domain>/api/integrations/iot/signals", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.UNIASSET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(uniassetPayload),
});
}
Step 5 — Verify signals are arriving
- In UniAsset, open IoT Signals in the left sidebar (only visible on Enterprise).
- The timeline should show messages within seconds of devices publishing.
- If you mapped
assetId, open the corresponding asset in Assets → <name> and scroll to the IoT Signals section.
Best practices
- Store the API key in Azure Key Vault. Do not check it into source control or paste it into the IoT Hub UI without a Key Vault reference.
- Use one API key per environment. Production and staging should have distinct keys so revocation is surgical.
- Adopt a dotted-path
signalTypeconvention. It future-proofs rule patterns:temperature.high,temperature.low,vibration.spike,door.open,motor.runtime. - Send severities as upper case enums —
CRITICAL,HIGH,MEDIUM,LOW,INFO. UniAsset will normalize anything else, but consistency simplifies your downstream rules later. - Keep payloads under 1 MB. UniAsset rejects larger bodies with a
413 PAYLOAD_TOO_LARGE. For aggregate uploads, split at the IoT Hub side.
Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
401 INVALID_KEY | Key revoked or mistyped | Regenerate the key in UniAsset and update the IoT Hub endpoint header. |
403 ENTERPRISE_REQUIRED | Plan downgraded | Restore the Enterprise plan. |
403 FORBIDDEN | Key missing iot:signal:ingest | Edit the key's permissions in UniAsset. |
404 NOT_FOUND on a signal | Supplied assetId doesn't belong to the tenant | Remove the assetId or correct the mapping. The signal will still be stored without an asset link if you drop the field. |
422 VALIDATION_ERROR | Missing source / signalType / payload, or signalType contains invalid characters | Check the response details[] for the offending field and fix the transformation function. |
413 PAYLOAD_TOO_LARGE | Body above 1 MB | Split the payload upstream. |
| Signals visible globally but not on an asset detail page | Missing or wrong assetId | Add assetId to the payload before posting. |
Related articles
Need Help?
If you have questions not covered in this article, our support team is here to help.
Contact Support