MachineQ Developer Documentation logo DOCS

Data Delivery: Webhooks & Real-Time Events

MQcentral delivers device data and gateway events to your endpoints in real time through two push mechanisms: Output Profiles (for device uplinks) and the Output Alert Service (for gateway connectivity events). This page covers the delivery formats, delivery semantics, and patterns for building receivers that hold up in production.

Before You Begin

You need:

Delivery Mechanisms

MQcentral uses two distinct push mechanisms:

Mechanism Data delivered Where to configure
Output Profile: RestParams (v1) Device uplinks as they are received Data Delivery: Output Profiles
Output Alert Service: webhook destination (v2) Gateway online/offline events Gateway & Network Provisioning

Each mechanism uses a different base URL and configuration model. They are not interchangeable.

When an Output Profile RestParams target fires, your endpoint receives an HTTP POST with a JSON body. The payload structure matches the uplink format documented in Working with Device Data:

  • extended format includes decoded ApplicationData alongside raw network metadata
  • simple format includes only the decoded payload

Use the combination (DevEUI, FCnt) as your idempotency key; this pair is unique per uplink and survives retries and multi-gateway duplicate delivery.

Gateway Event Payload Format

When a gateway's connectivity changes, the Output Alert Service sends a POST HTTP request to all configured webhook destinations:

Gateway Event Payload Example
{
  "timestamp": "2022-09-12T14:49:01.964Z",
  "type": "gateway_connectivity",
  "data": {
    "timestamp": "2022-09-12T14:49:01.919Z",
    "node_id": "2CC4070000001234",
    "status": "online"
  }
}

data.status is "online" or "offline". Gateway connectivity alerts include a 15-minute debounce; see Gateway & Network Provisioning for full details.

Delivery Semantics

  • At-least-once. A given uplink or event may be delivered more than once, most commonly when your endpoint times out or returns 5xx. Your receiver must be idempotent.
  • Best-effort ordering. Events for the same device usually arrive in order, but retries can cause reordering. Don't rely on order for correctness; if it matters, sort by timestamp or FCnt.
  • Retries on failure. Failed deliveries retry on 5xx responses or timeouts. Monitor your endpoint's error rate; a sustained failure rate means data is accumulating in the retry queue.

Building a Resilient Receiver

Acknowledge fast, process async. Your handler should do as little as possible: validate the request, drop the event onto an internal queue (SQS, Pub/Sub, RabbitMQ), and return 200. Processing synchronously converts every downstream slowdown into a delivery timeout and retry storm.

Idempotency on (DevEUI, FCnt). Track recently-seen (DevEUI, FCnt) pairs in a short-TTL cache (for example, a Redis key per pair with a five-minute expiry, SET deveui:fcnt 1 EX 300) and drop any delivery whose key already exists. The TTL keeps memory bounded while still catching duplicates within the platform's retry window. This is non-negotiable in production.

Dead-letter handling. Decide in advance what happens to events that exhaust retries. Silently dropping them is how data quality issues compound undetected. At minimum, log them durably and alert on sustained delivery failures.

Return 5xx for backpressure. If your downstream system is overloaded, return 5xx and let the platform back off rather than accepting and losing the delivery internally. The retry mechanism is the only thing between your incident and permanent data loss.

MQTT as an Alternative

For high-volume telemetry streams where HTTP overhead is a concern, configure an Output Profile with a MqttParams target instead. MQTT trades complexity (you operate a long-lived connection with reconnect logic) for lower per-message overhead and a single delivery channel.

A practical split: use REST/webhook delivery for lower-volume, event-driven integrations; use MQTT for continuous high-frequency sensor streams.

Both are configured through Output Profiles. See Data Delivery: Output Profiles.

Testing Locally

Testing Output Profile Receivers

Output Profiles can deliver device uplinks to four target types: a REST endpoint (RestParams), an MQTT broker (MqttParams), Azure IoT Hub (AzureParams), or AWS IoT Core (AWSParams). The approach to local testing depends on which target you use.

HTTP / REST receiver. Start your webhook handler on localhost:8080 (or whichever port you choose) so it's ready to accept POST requests. The platform delivers uplinks in the format returned by the v1 Logs API. Here's the full payload shape:

Uplink Delivery Payload
{
  "Timestamp": "2026-05-10T01:32:28.290Z",
  "DevEUI": "2CC407FFFE500D87",
  "DevAddr": "446BC633",
  "Fport": "2",
  "FCnt": "721",
  "MessageType": "2",
  "MessageTypeText": "UnconfirmedDataUp",
  "PayloadHex": "4d",
  "MICHex": "41de6fe9",
  "PrimaryGatewayRSSI": "0.0",
  "PrimaryGatewaySNR": "0.0",
  "PrimaryGatewayESP": "0.0",
  "SpreadingFactor": "7",
  "Airtime": "0.046336",
  "SubBand": "G0",
  "Channel": "LC1",
  "GatewayID": "Not Owned",
  "GatewayLatitide": "null",
  "GatewayLongitude": "null",
  "GatewayCount": "1",
  "GatewayList": [
    {
      "Gateway": "07020000",
      "RSSI": "-85.0",
      "SNR": "13.25",
      "ESP": "-85.600769",
      "Time": "2026-05-10T01:32:28.285Z",
      "GatewayNodeID": "2CC4070000020000"
    }
  ],
  "DeviceLatitude": "null",
  "DeviceLongitude": "null",
  "DeviceLocationRadius": "null",
  "MacCommands": "",
  "DecodedMacCommands": [],
  "ADRbit": "1",
  "ADRAckReq": "0",
  "AckRequested": "0",
  "ACKbit": "0",
  "FPending": "null",
  "Late": "0",
  "DevNonce": "null",
  "JoinEUI": "null",
  "GatewayUnowned": true,
  "GatewayNodeID": "Not Owned",
  "PayloadDecoded": {
    "data": {
      "humidity": 38.5,
      "payload_type": "sensor_data"
    }
  }
}

Simulate a delivery with curl against your running server:

Simulate an Uplink Delivery
curl -X POST http://localhost:8080/webhook \
  -H "Content-Type: application/json" \
  -d '{"Timestamp":"2026-05-10T01:32:28.290Z","DevEUI":"2CC407FFFE500D87","FCnt":"721","PayloadHex":"4d","PayloadDecoded":{"data":{"humidity":38.5,"payload_type":"sensor_data"}}, ...}'

Send the same payload twice to confirm your deduplication cache rejects the second delivery. To receive real platform traffic locally, expose your server with ngrok or cloudflared and point your Output Profile at the tunnel URL (e.g., https://f6e0-160-154-163-253.ngrok-free.app):

Start an ngrok Tunnel
ngrok http 8080

MQTT receiver. Run a broker locally (e.g., Mosquitto in a container), then expose it with ngrok so the platform can reach it:

Expose a Local MQTT Broker
ngrok tcp 1883

Copy the public URL that ngrok returns (e.g., tcp://0.tcp.ngrok.io:12345) and set it as the broker URL in your Output Profile's MqttParams. The platform will publish device uplinks for the devices in that Output Profile to the ngrok address, which forwards them to your local broker. Once validated, swap the MqttParams URL to your production broker.

Azure IoT Hub / AWS IoT Core. These targets deliver directly into your cloud provider's IoT ingestion service; there is no local server to run. Test by pointing the Output Profile at a development IoT Hub or IoT Core instance and verifying messages arrive in the corresponding device stream or rule action.

Production Readiness Checklist. Before moving to production, verify your Output Profile receiver handles:

  1. A successful end-to-end delivery with the payload parsed correctly
  2. A duplicate event (same DevEUI + FCnt arriving twice) rejected by your idempotency cache
  3. A 5xx return followed by a platform retry and eventual success (HTTP targets)
  4. An MQTT disconnect and automatic reconnect (MQTT targets)

Testing Gateway Alert Receivers

Gateway alerts are delivered exclusively via webhook; your receiver is always an HTTP endpoint. The payload shape is defined in the v2 API specification and follows this structure:

Gateway Alert Event Payload
{
  "timestamp": "2023-01-10T14:58:35.455152Z",
  "type": "gateway_connectivity",
  "data": {
    "timestamp": "2023-01-10T14:58:29.076699Z",
    "node_id": "FFFFA15A9C20F79F",
    "status": "offline"
  }
}

type is always "gateway_connectivity". data.status is "online" or "offline".

Simulate a gateway alert locally with curl:

Simulate a Gateway Alert
curl -X POST http://localhost:8080/alerts \
  -H "Content-Type: application/json" \
  -d '{
    "timestamp": "2023-01-10T14:58:35.455152Z",
    "type": "gateway_connectivity",
    "data": {
      "timestamp": "2023-01-10T14:58:29.076699Z",
      "node_id": "FFFFA15A9C20F79F",
      "status": "offline"
    }
  }'

To receive real alerts locally, expose your server with ngrok or cloudflared and configure the tunnel URL (e.g., https://f6e0-160-154-163-253.ngrok-free.app) as an Output Alert destination.

Production Readiness Checklist. Before moving to production, verify your alert receiver:

  1. Parses the gateway_connectivity event correctly
  2. Returns 200 promptly to acknowledge receipt
  3. Handles a 5xx scenario followed by a platform retry and eventual success

General Tips

Log every inbound request during development. Dump the full request body and headers to stdout or a file. When something doesn't parse, having the raw payload makes debugging immediate instead of speculative.

What's Next?