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

HeaderRequiredDescription
X-API-KeyYesYour organization's API key
Content-TypeYesapplication/json

Supported formats

DataKubo detects the format automatically from the payload keys:

FormatDetected by
ChirpStack v4deviceInfo 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 FieldNormalized FieldNotes
deviceInfo.devEuidev_euiRequired — 16 hex characters, stored lowercase
deviceInfo.tenantNametenant_nameRequired — maps to your organization or a community
deviceInfo.deviceNamedevice_nameRequired
timetimestampRequired — ISO 8601
object.cumulativeFlowM3consumption_m3Preferred — total meter reading in cubic meters
object.cumulativeFlowLitersconsumption_m3Total meter reading in liters, auto-converted to m³
object.batteryVoltagebattery_levelVolts
object.latitude / longitude / altitudeDevice locationOptional — latitude −90 to 90, longitude −180 to 180
object.alarm_*Hardware alarmstrue/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 FieldNormalized FieldNotes
end_device_ids.dev_euidev_euiRequired — 16 hex characters, stored lowercase
end_device_ids.application_ids.application_idtenant_nameRequired — maps to your organization or a community
received_attimestampRequired — ISO 8601
cumulativeFlowM3consumption_m3Preferred — total meter reading in cubic meters
waterconsumption_m3B-Meters — total meter reading in cubic meters
cumulativeFlowconsumption_m3Xinos — total meter reading in liters, auto-converted to m³
battery / bateriabattery_levelVoltage value
battery_statusbattery_level"OK" → 3.6 V, anything else → 2.8 V
alarm_*Hardware alarmstrue/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 fieldAlarmSeverity
alarm_burstBurstCritical
alarm_overflowOverflowCritical
alarm_reverse_flowReverse flowCritical
alarm_leakageLeakageWarning
alarm_low_batteryLow batteryWarning
alarm_wrong_installationWrong installationWarning

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 to true/false in your payload formatter.

How they're handled:

  • Triggered — logged when a flag changes from false to true.
  • 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

CodeError codeMeaning
200—Data ingested (or skipped: TTN message without decoded_payload, or uplink without a reading — see above)
400INVALID_PAYLOAD_FORMATNot a ChirpStack or TTN payload
400VALIDATION_ERRORMissing required field or invalid value (e.g. bad devEui, negative consumption)
401—Invalid or missing API key
403TENANT_MISMATCHTenant name doesn't match your organization or any community
403TRIAL_EXPIREDTrial has ended without an active subscription
429RATE_LIMIT_EXCEEDEDMonthly limit reached (100,000 requests)
500INTERNAL_ERRORInternal 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