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
Requirements
- Available on: Odyssey, Cosmos
- Roles: Owner, to create the API key
Important UniAsset stores and displays signals. Nothing reacts to them automatically — a signal does not raise a work order, open an incident, or send an alert. Keep threshold logic in Azure and treat UniAsset as the record it writes to.
Prerequisites
Before you begin, confirm you have:
- A UniAsset organization on Odyssey or Cosmos
- 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.
- Go to Settings → Integrations → API Keys.
- Click New API Key.
- Give it a descriptive name (for example,
Azure IoT Hub — Production). - Grant only the
iot:signal:ingestpermission. Do not add write permissions this integration does not need. - Create it. The full key is shown once — copy it now and store it in Azure Key Vault. UniAsset stores only a short lookup prefix and a hash, so the key cannot be recovered afterwards.
The key format is ua_live_ followed by 64 characters.
See How to create API keys for the full reference.
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 the vocabulary stays consistent as you scale. |
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, go to Dashboard → IoT Signals (visible on Odyssey and above).
- 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 normalizes anything else, but consistency makes the signal list easier to read and filter. - 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 / 403 PLAN_UPGRADE_REQUIRED | Plan downgraded | Restore the Odyssey or Cosmos 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