LoRaWAN Integration
Connect any LoRaWAN sensor to HydroNode with a single HTTPS webhook. Works with The Things Network, ChirpStack, and Helium. No AWS account required.
One unified webhook endpoint
HydroNode exposes a single webhook URL that auto-detects whether the incoming uplink is from TTN (v3) or ChirpStack (v4/v3). There are no separate endpoints and no per-network configuration.
POST https://hydronode.texhfexlabs.de/api/webhook/lorawan/uplink/{webhookToken}wht_…) in the URL proves the request belongs to your account. Never expires unless you rotate it. Before you start
- 1A sensor exists in HydroNodeCreate the sensor via the dashboard or API first.
- 2The sensor is saved as OutdoorOpen General settings, select Outdoor, and save. A LoRaWAN binding creates a Station Interface automatically, and only outdoor sensors can be stations.
- 3The physical DevEUI is at hand16 hex characters printed on the device, e.g.
a840411b2182cd44. - 4You know your payload layoutWhich fPort carries which channels and at which byte offsets (configured in Step 4).
Register your device on the network server
This is the one step that differs between networks.
- Open console.thethings.network and select your application.
- Go to End devices → Add end device.
- Enter JoinEUI / AppEUI, DevEUI, and AppKey from the device firmware.
- Complete registration, then note the DevEUI and Application ID.
- Log in to your ChirpStack instance and open (or create) an Application.
- Ensure a Device profile matches your device's LoRaWAN version.
- Go to Devices → Add device and enter the DevEUI + profile.
- Under the device's Keys tab set the AppKey (OTAA) or session keys (ABP). Note the DevEUI and Application ID.
Bind the device to your sensor
Done entirely in the HydroNode web app. Open your sensor and go to its Settings page, then use the LoRaWAN Binding card.
- 1Confirm the sensor is OutdoorIf it is still Indoor, change it under General settings and save before continuing.
- 2Enter the DevEUIThe 16 lowercase hex characters from your device (and noted in Step 02).
- 3Enter the LoRaWAN Application ID (optional)The Application ID from TTN or ChirpStack. Only needed if several applications share a DevEUI range; otherwise leave it blank.
- 4Click Save LoRaWAN BindingHydroNode creates the Station Interface automatically, generates your personal Webhook URL, and shows it in the same connection card.
- 5Reveal and copy the Webhook URLClick the eye icon to reveal it, then Copy. You paste this into the network server in Step 04.
Point the network server at your URL
- Application → Integrations → Webhooks → Add webhook → Custom webhook.
- Webhook format: JSON.
- Base URL: paste the Webhook URL you copied in Step 03.
- Enable Uplink message. Leave everything else default. No headers needed.
- Save changes.
Only devices whose DevEUI is registered in HydroNode are processed; the rest get a silent 401.
- Application → Integrations → HTTP.
- Event endpoint URL(s): paste the Webhook URL you copied in Step 03.
- Headers: leave empty.
- Enabled events: select up (uplink) at minimum.
- Submit.
Non-uplink events (join, ack…) return 400 and are safely ignored by ChirpStack.
Configure fPort decoding
Each fPort carries a fixed set of channels packed into the payload bytes. You tell HydroNode how to decode them in the web app: on the same sensor Settings page, scroll to fPort Configurations. See Sensor Types for the supported types and encodings.
- 1Click Add fPortSet the fPort number (1–223) your firmware sends on, and an optional description.
- 2Add one channel per valuePick a Sensor Type (presets fill in bytes, encoding, scale and unit), then adjust Bytes (1, 2, 3 or 4), Encoding (signed or unsigned), Byte Order, Scale Factor, Offset, Channel Name and Unit if needed. Choose Custom for a type not in the list.
- 3Order the channelsChannels decode top to bottom with no gaps. Use the up and down arrows so the order matches the byte layout your device transmits.
- 4Click CreateRepeat for every fPort your device uses. Existing configs can be edited or deleted from the same list.
How a channel is decoded
Every channel reads a fixed number of bytes and turns them into one physical value:
value = raw × scaleFactor + offset| Field | Accepts | When you need it |
|---|---|---|
| Bytes | 1, 2, 3 or 4 | 1 byte suits battery percent, wind direction as an octant or a status flag. 4 bytes suit counters and high resolution values. |
| Encoding | Signed or unsigned | Signed reads the value as two's complement over the configured width, so a temperature can go below zero. |
| Byte Order | Big endian or little endian | Big endian is the default and is what most devices send. Milesight and several other vendors pack the least significant byte first; pick little endian for those instead of changing your firmware. |
| Scale Factor | Any number above zero | Converts the raw integer into the physical unit. A temperature sent in hundredths of a degree uses 0.01. |
| Offset | Any number, 0 by default | Added after scaling. Devices that bias their reading to avoid negative numbers need it: a device sending raw × 0.1 − 40 takes scale 0.1 and offset −40. |
400 no-fport-config and routed to the dead-letter queue. Configure the fPort before sending live data.Native payload formats (auto-detected)
No transformation needed. HydroNode reads each network's native JSON and picks the format from its shape: end_device_ids means The Things Stack v3, deviceInfo or a top level devEui means ChirpStack v4. Helium is not a third format; its console runs on ChirpStack and sends the ChirpStack event. Anything else is rejected with 400 rather than guessed at.
Repeat deliveries are recognised by deduplicationId when the network server sends one, and by the frame counter otherwise. If a payload carries neither, deduplication is skipped for that uplink: a duplicate reading can be removed later, a dropped one cannot be recovered.
{
"end_device_ids": {
"dev_eui": "a840411b2182cd44",
"application_ids": {
"application_id": "my-app"
}
},
"uplink_message": {
"f_port": 1,
"f_cnt": 9846,
"frm_payload": "AQH0AgBk",
"rx_metadata": [
{ "rssi": -85, "snr": 7.2 }
],
"received_at": "2026-07-21T11:24:34.027Z"
},
"received_at": "2026-07-21T11:24:39.500Z"
}{
"deduplicationId": "f2b0b8ec-1d5a-4c1f-9d0e-2f2f7d3a1b22",
"time": "2026-07-21T11:24:34.027+00:00",
"deviceInfo": {
"devEui": "a840411b2182cd44",
"applicationId": "abc123"
},
"fPort": 1,
"fCnt": 9846,
"data": "AQH0AgBk",
"rxInfo": [
{ "rssi": -85, "snr": 7.2 }
]
}Confirm it works
Trigger a test uplink (TTN's Simulate uplink or a real transmission). On success, the server logs LoRaWAN uplink processed: network=… devEui=… fPort=… and the value appears under the sensor.
A 202 means the values are stored, not merely received. HydroNode waits for its own storage to confirm them before answering, so a 202 is a real delivery receipt. If storage is unavailable you get 503 instead and your network server should redeliver. Repeated deliveries of the same frame are recognised and stored once, so retrying is always safe.
| Code | Meaning | What to do |
|---|---|---|
| 202 | Uplink received and queued | Nothing, success |
| 400 | no-fport-config | Configure the fPort (Step 05) |
| 400 | Missing payload / Invalid fPort | Join or MAC frame, safe to ignore |
| 400 | Payload error | Check the channel order in the fPort config |
| 401 | Unknown device / Unauthorized | Bind the DevEUI (Step 03) or check the token in the URL |
| 403 | Device not owned by token user | DevEUI belongs to another account |
| 429 | Too many requests | You are sending faster than the limit allows. Honour Retry-After and slow down. A normal device reports every few minutes and never reaches this. |
| 503 | Upstream unavailable, retry | HydroNode could not store the values and is asking for the uplink again. Configure your network server to retry; the same uplink is accepted on the next delivery and duplicates are filtered out. |
| 500 | Internal error | Check HydroNode logs |
202 arrives and a value shows up on the sensor, the integration is live. Every other device in the same application that you register in HydroNode starts flowing automatically.