Webhook Integration
DataKubo ingests device data via HTTP webhooks. Point your LoRaWAN network server at our ingestion endpoint, and we handle normalization, storage, and device auto-registration.
Endpoint
POST https://api.datakubo.com/api/v1/ingest/water-consumption
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your organization's API key |
Content-Type | Yes | application/json |
Supported formats
DataKubo detects the format automatically from the payload keys:
| Format | Detected by |
|---|---|
| ChirpStack v4 | deviceInfo and object |
| The Things Network (TTN v3) | end_device_ids and uplink_message |
Any other payload is rejected with 400 INVALID_PAYLOAD_FORMAT. Extra fields (for example rxInfo, txInfo, rx_metadata) are ignored.
ChirpStack payload
ChirpStack sends an up event when a device transmits data.
{
"deviceInfo": {
"tenantName": "your-tenant",
"deviceName": "Water Meter #1",
"devEui": "a1b2c3d4e5f60001"
},
"time": "2025-08-04T20:34:35.816710+00:00",
"object": {
"cumulativeFlowM3": 123.456,
"batteryVoltage": 3.6,
"latitude": 40.416775,
"longitude": -3.70379,
"altitude": 667.5
}
}
ChirpStack field mapping
| ChirpStack Field | Normalized Field | Notes |
|---|---|---|
deviceInfo.devEui | dev_eui | Required — 16 hex characters, stored lowercase |
deviceInfo.tenantName | tenant_name | Required — maps to your organization or a community |
deviceInfo.deviceName | device_name | Required |
time | timestamp | Required — ISO 8601 |
object.cumulativeFlowM3 | consumption_m3 | Preferred — total meter reading in cubic meters |
object.cumulativeFlowLiters | consumption_m3 | Total meter reading in liters, auto-converted to m³ |
object.batteryVoltage | battery_level | Volts |
object.latitude / longitude / altitude | Device location | Optional — latitude −90 to 90, longitude −180 to 180 |
object.alarm_* | Hardware alarms | true/false (or 1/0) — see Hardware alarms |
ChirpStack only reads the field names above. A decoder that outputs water, cumulativeFlow or battery works on TTN but not on ChirpStack — rename them in your codec. The alarm_* fields work on both.
TTN payload
The Things Network sends uplink messages with a decoded_payload field.
{
"end_device_ids": {
"dev_eui": "A1B2C3D4E5F60001",
"application_ids": {
"application_id": "your-tenant"
}
},
"uplink_message": {
"decoded_payload": {
"cumulativeFlowM3": 123.456,
"battery": 3.6
}
},
"received_at": "2025-08-04T20:34:35.816710+00:00"
}
TTN field mapping
| TTN Field | Normalized Field | Notes |
|---|---|---|
end_device_ids.dev_eui | dev_eui | Required — 16 hex characters, stored lowercase |
end_device_ids.application_ids.application_id | tenant_name | Required — maps to your organization or a community |
received_at | timestamp | Required — ISO 8601 |
cumulativeFlowM3 | consumption_m3 | Preferred — total meter reading in cubic meters |
water | consumption_m3 | B-Meters — total meter reading in cubic meters |
cumulativeFlow | consumption_m3 | Xinos — total meter reading in liters, auto-converted to m³ |
battery / bateria | battery_level | Voltage value |
battery_status | battery_level | "OK" → 3.6 V, anything else → 2.8 V |
alarm_* | Hardware alarms | true/false (or 1/0) — see Hardware alarms |
When several consumption or battery fields are present, the first one in the table wins. TTN payloads carry no GPS location; latitude/longitude in a TTN decoder are ignored.
TTN messages without a decoded_payload are acknowledged (HTTP 200) but skipped — no data is stored. Configure a payload formatter in the TTN console.
Uplinks with no consumption field (none of the fields above, or all null) are acknowledged with HTTP 200 and "skipped": true, "reason": "no_reading". No reading is stored, but the device's last-seen time, battery and alarms are updated. An explicit 0 is a real reading and is stored.
What gets stored
- Consumption is always stored in cubic meters (m³). Send the meter's total reading — the index on its display — not the volume used since the previous uplink. DataKubo works out consumption from the difference between readings.
- New devices are registered automatically on their first uplink. If the tenant name matches a community, the device is linked to it.
- Deleted devices that send data again are reactivated.
- GPS location is read from ChirpStack payloads only.
If a payload has none of the supported consumption fields, the reading is currently stored as 0 m³. Make sure your decoder outputs one of the field names above.
Hardware alarms
Water meters report their own alarms (burst, leak, etc.). DataKubo reads them as booleans from the ChirpStack object or the TTN decoded_payload — same field names for both:
| Payload field | Alarm | Severity |
|---|---|---|
alarm_burst | Burst | Critical |
alarm_overflow | Overflow | Critical |
alarm_reverse_flow | Reverse flow | Critical |
alarm_leakage | Leakage | Warning |
alarm_low_battery | Low battery | Warning |
alarm_wrong_installation | Wrong installation | Warning |
Send true or false (1/0 and "true"/"false" also work). Any other value — for example a status text like "OK" or a bitmask — is handled differently per format:
- ChirpStack — the value is ignored and the reading is still stored.
- TTN — the whole uplink is rejected with
400 VALIDATION_ERROR. Convert the value totrue/falsein your payload formatter.
How they're handled:
- Triggered — logged when a flag changes from
falsetotrue. - Cleared — logged when a flag changes back to
false, or stops being sent. The meter is the source of truth, so there is no manual acknowledge. - Alarms are logged even when the device is in maintenance.
- Each event stores the reading and battery level at that moment.
Response codes
| Code | Error code | Meaning |
|---|---|---|
200 | — | Data ingested (or skipped: TTN message without decoded_payload, or uplink without a reading — see above) |
400 | INVALID_PAYLOAD_FORMAT | Not a ChirpStack or TTN payload |
400 | VALIDATION_ERROR | Missing required field or invalid value (e.g. bad devEui, negative consumption) |
401 | — | Invalid or missing API key |
403 | TENANT_MISMATCH | Tenant name doesn't match your organization or any community |
403 | TRIAL_EXPIRED | Trial has ended without an active subscription |
429 | RATE_LIMIT_EXCEEDED | Monthly limit reached (100,000 requests) |
500 | INTERNAL_ERROR | Internal server error |
Rate limits
Webhook ingestion is rate-limited to 100,000 requests per month per organization. Only successful requests count. The counter resets on the 1st of each month (UTC).
Rate limit headers are included in every ingestion response:
X-RateLimit-Limit: 100000
X-RateLimit-Remaining: 99847
X-RateLimit-Reset: 2025-02-01T00:00:00Z
Dashboard API calls are not counted against your rate limit — only webhook ingestion requests count.
Next steps
- ChirpStack Guide — step-by-step ChirpStack configuration
- TTN Guide — step-by-step TTN configuration
- LoRaWAN Payload Decoder — check a webhook payload before sending it
- API Reference — all available endpoints