# Send your first reading (https://logable.app/docs/integrate/quickstart)

From a device token to a value on a dashboard in five minutes, with HTTP or MQTT.



This page takes you from a new device to a value on its dashboard. Pick HTTP or MQTT. Both accept the same reading.

## What you need [#what-you-need]

You need a device in Logable, with its id and its token. Create the device, then copy both from its **Connection** tab. See [Connect a device](/docs/guides/devices/connect-a-device).

## Over HTTP [#over-http]

Send one POST request with the reading in the body.

#### cURL

```bash
# Raw Meal Silo - Quick Test (curl)
# Device Type: silo | Device ID: <DEVICE_ID>
#
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.

curl -X POST "https://ingest.logable.app/ingest/<DEVICE_ID>" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <DEVICE_TOKEN>" \
  -d '{"timestamp":"2026-07-01T10:00:00.000Z","status":"online","data":{"weight":18750.5,"status":"filling"}}'
```

#### Python

```python
"""
Raw Meal Silo - Logable HTTP Integration (Python)
Device Type: silo
Device ID: <DEVICE_ID>

Installation: pip install requests
"""

import requests
from datetime import datetime, timezone

API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN = "<DEVICE_TOKEN>"

def send_data():
    # The "data" fields shown are this device type's registered metrics.
    # Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
    # Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
    # Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
    # Custom values appear on dashboards via a widget's "Show Custom Data" option.
    payload = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "status": "online",
        "data": {
            "weight": 18750.5,
            "status": "filling"
        }
    }

    response = requests.post(
        API_ENDPOINT,
        json=payload,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {API_TOKEN}"
        },
        timeout=10
    )

    response.raise_for_status()
    print(f"Data sent successfully: {response.status_code}")

if __name__ == "__main__":
    send_data()
```

#### TypeScript

```typescript
/**
 * Raw Meal Silo - Logable HTTP Integration (TypeScript)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Runtime: Node.js 18+ or modern browser (uses built-in fetch)
 */

const API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const API_TOKEN = "<DEVICE_TOKEN>";

async function sendData(): Promise<void> {
  // The "data" fields shown are this device type's registered metrics.
  // Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
  // Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
  // Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
  // Custom values appear on dashboards via a widget's "Show Custom Data" option.
  const payload = {
    timestamp: new Date().toISOString(),
    status: "online",
    data: {
      "weight": 18750.5,
      "status": "filling"
    },
  };

  const response = await fetch(API_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${API_TOKEN}`,
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  console.log("Data sent successfully:", response.status);
}

sendData();
```

#### Bash

```bash
#!/bin/bash
# Raw Meal Silo - Logable HTTP Integration (Bash)
# Device Type: silo
# Device ID: <DEVICE_ID>

API_ENDPOINT="https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN="<DEVICE_TOKEN>"

# NOTE: the heredoc below is pure JSON, so it cannot contain comments.
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
# Quoted heredoc delimiter: '$' in metric values is taken literally.
# "timestamp" is optional - the platform stamps arrival time when omitted.
read -r -d '' PAYLOAD <<'EOF'
{
  "status": "online",
  "data": {
    "weight": 18750.5,
    "status": "filling"
  }
}
EOF

HTTP_CODE=$(curl -s -w "%{http_code}" -o /dev/null \
  -X POST "${API_ENDPOINT}" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -d "${PAYLOAD}")

if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
  echo "Data sent successfully (HTTP ${HTTP_CODE})"
  exit 0
else
  echo "Error sending data (HTTP ${HTTP_CODE})"
  exit 1
fi
```

#### PowerShell

```powershell
<#
.SYNOPSIS
Raw Meal Silo - Logable HTTP Integration (PowerShell)
Device Type: silo
Device ID: <DEVICE_ID>
#>

$ApiEndpoint = "https://ingest.logable.app/ingest/<DEVICE_ID>"
$ApiToken = "<DEVICE_TOKEN>"

function Send-DeviceData {
    $Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")

    # The "data" fields shown are this device type's registered metrics.
    # Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
    # Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
    # Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
    # Custom values appear on dashboards via a widget's "Show Custom Data" option.
    $Payload = @{
        timestamp = $Timestamp
        status = "online"
        data = @{
            weight = 18750.5
            status = 'filling'
        }
    } | ConvertTo-Json -Depth 3

    $Headers = @{
        "Content-Type" = "application/json"
        "Authorization" = "Bearer $ApiToken"
    }

    try {
        $Response = Invoke-RestMethod `
            -Uri $ApiEndpoint `
            -Method Post `
            -Headers $Headers `
            -Body $Payload `
            -TimeoutSec 10

        Write-Host "Data sent successfully" -ForegroundColor Green
        return $Response
    } catch {
        Write-Error "Error: $($_.Exception.Message)"
        throw
    }
}

Send-DeviceData
```

#### C (libcurl)

```c
/*
 * Raw Meal Silo - Logable HTTP Integration (C / libcurl)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Install: sudo apt-get install libcurl4-openssl-dev
 * Compile: gcc -o device_client device_client.c -lcurl
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

#define API_ENDPOINT "https://ingest.logable.app/ingest/<DEVICE_ID>"
#define API_TOKEN "<DEVICE_TOKEN>"

/* Example metric payload - CUSTOMIZE DATA HERE */
static const char *DATA_JSON =
    "{\"weight\":18750.5,\"status\":\"filling\"}";

int main(void) {
    CURL *curl;
    CURLcode res;
    struct curl_slist *headers = NULL;

    time_t now = time(NULL);
    char timestamp[64];
    strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));

    /* Build JSON payload - CUSTOMIZE DATA HERE */
    /*
     * The "data" fields shown are this device type's registered metrics.
     * Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
     * Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
     * Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
     * Custom values appear on dashboards via a widget's "Show Custom Data" option.
     */
    /* Metric data is passed as a %s argument (never spliced into the
     * format string), so '%' characters in values stay intact. */
    char payload[1024];
    snprintf(payload, sizeof(payload),
        "{\"timestamp\": \"%s\", \"status\": \"online\", \"data\": %s}",
        timestamp, DATA_JSON);

    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();

    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, API_ENDPOINT);
        headers = curl_slist_append(headers, "Content-Type: application/json");
        char auth_header[256];
        snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", API_TOKEN);
        headers = curl_slist_append(headers, auth_header);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);

        res = curl_easy_perform(curl);
        if(res != CURLE_OK) {
            fprintf(stderr, "Request failed: %s\n", curl_easy_strerror(res));
        } else {
            printf("Data sent successfully\n");
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}
```

#### Arduino / ESP32

```cpp
/*
 * Raw Meal Silo - Logable HTTP Integration (Arduino / ESP32)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Libraries: WiFi, HTTPClient, ArduinoJson
 * Board: ESP32 or ESP8266
 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const char* API_TOKEN = "<DEVICE_TOKEN>";

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" Connected!");
}

void sendMetrics() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi not connected");
    return;
  }

  HTTPClient http;
  http.begin(API_ENDPOINT);
  http.addHeader("Content-Type", "application/json");

  char authHeader[256];
  snprintf(authHeader, sizeof(authHeader), "Bearer %s", API_TOKEN);
  http.addHeader("Authorization", authHeader);

  /* Build JSON payload - CUSTOMIZE DATA HERE */
  JsonDocument doc;
  doc["status"] = "online";
  doc.createNestedObject("data");
  doc["data"]["weight"] = 18750.5;
  doc["data"]["status"] = "filling";
  // The "data" fields shown are this device type's registered metrics.
  // Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
  // Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
  // Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
  // Custom values appear on dashboards via a widget's "Show Custom Data" option.

  String payload;
  serializeJson(doc, payload);

  int httpCode = http.POST(payload);
  if (httpCode > 0) {
    Serial.printf("Data sent (HTTP %d)\n", httpCode);
  } else {
    Serial.printf("HTTP error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end();
}

void loop() {
  sendMetrics();
  delay(10000); /* Send every 10 seconds */
}
```

A successful call returns this body:

```json title="Response"
{
  "acknowledged": true,
  "timestamp": "2026-07-01T10:00:00.123Z",
  "deviceId": "<DEVICE_ID>"
}
```

Open the device's **Overview** tab. The value appears without a refresh.

## Over MQTT [#over-mqtt]

Connect to the broker and publish on the device's own topic.

Table: see https://logable.app/docs/integrate/quickstart#over-mqtt

#### Python

```python
"""
Raw Meal Silo - Logable MQTT Integration (Python)
Device Type: silo
Device ID: <DEVICE_ID>

Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
Client ID must be unique per connection - a duplicate ID disconnects the other client.
Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.

Installation: pip install paho-mqtt
"""

import paho.mqtt.client as mqtt
import json
from datetime import datetime, timezone
import time

MQTT_HOST = "mqtt.logable.app"
MQTT_PORT = 8883
MQTT_USERNAME = "<DEVICE_ID>"
MQTT_PASSWORD = "<DEVICE_TOKEN>"
MQTT_CLIENT_ID = "logable-<DEVICE_ID>"
MQTT_TOPIC = "devices/<DEVICE_ID>/data"

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection failed: {reason_code}")
    else:
        print(f"Connected to MQTT broker at {MQTT_HOST}:{MQTT_PORT}")

def send_data(client):
    # The "data" fields shown are this device type's registered metrics.
    # Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
    # Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
    # Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
    # Custom values appear on dashboards via a widget's "Show Custom Data" option.
    payload = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "status": "online",
        "data": {
            "weight": 18750.5,
            "status": "filling"
        }
    }
    result = client.publish(MQTT_TOPIC, json.dumps(payload), qos=1)
    if result.rc == mqtt.MQTT_ERR_SUCCESS:
        print(f"Data sent to {MQTT_TOPIC}")

def main():
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=MQTT_CLIENT_ID)
    client.on_connect = on_connect
    client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
    # TLS with the default system CA store - validates the broker's
    # Let's Encrypt certificate out of the box.
    client.tls_set()

    try:
        client.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
        client.loop_start()
        time.sleep(1)
        send_data(client)
        time.sleep(1)
        client.loop_stop()
        client.disconnect()
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()
```

#### TypeScript

```typescript
/**
 * Raw Meal Silo - Logable MQTT Integration (TypeScript)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
 * Client ID must be unique per connection - a duplicate ID disconnects the other client.
 * Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
 *
 * Installation: npm install mqtt
 */

import mqtt from 'mqtt';

// mqtts:// - the mqtt package handles TLS and CA validation automatically.
const MQTT_URL = "mqtts://mqtt.logable.app:8883";
const MQTT_USERNAME = "<DEVICE_ID>";
const MQTT_PASSWORD = "<DEVICE_TOKEN>";
const MQTT_CLIENT_ID = "logable-<DEVICE_ID>";
const MQTT_TOPIC = "devices/<DEVICE_ID>/data";

async function sendData(): Promise<void> {
  const client = mqtt.connect(MQTT_URL, {
    clientId: MQTT_CLIENT_ID,
    username: MQTT_USERNAME,
    password: MQTT_PASSWORD,
    clean: true,
  });

  client.on('connect', () => {
    console.log(`Connected to MQTT broker at ${MQTT_URL}`);

    // The "data" fields shown are this device type's registered metrics.
    // Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
    // Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
    // Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
    // Custom values appear on dashboards via a widget's "Show Custom Data" option.
    const payload = {
      timestamp: new Date().toISOString(),
      status: "online",
      data: {
      "weight": 18750.5,
      "status": "filling"
      },
    };

    client.publish(
      MQTT_TOPIC,
      JSON.stringify(payload),
      { qos: 1 },
      (error) => {
        if (error) console.error('Failed to publish:', error);
        else console.log(`Data sent to ${MQTT_TOPIC}`);
        client.end();
      }
    );
  });

  client.on('error', (error) => {
    console.error('MQTT error:', error);
    client.end();
  });
}

sendData();
```

#### Bash

```bash
#!/bin/bash
# Raw Meal Silo - Logable MQTT Integration (Bash)
# Device Type: silo
# Device ID: <DEVICE_ID>
#
# Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
# Client ID must be unique per connection - a duplicate ID disconnects the other client.
# Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
#
# Requirements: mosquitto-clients

MQTT_HOST="mqtt.logable.app"
MQTT_PORT=8883
MQTT_USERNAME="<DEVICE_ID>"
MQTT_PASSWORD="<DEVICE_TOKEN>"
MQTT_CLIENT_ID="logable-<DEVICE_ID>"
MQTT_TOPIC="devices/<DEVICE_ID>/data"

# NOTE: the heredoc below is pure JSON, so it cannot contain comments.
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
# Quoted heredoc delimiter: '$' in metric values is taken literally.
# "timestamp" is optional - the platform stamps arrival time when omitted.
read -r -d '' PAYLOAD <<'EOF'
{
  "status": "online",
  "data": {
    "weight": 18750.5,
    "status": "filling"
  }
}
EOF

# TLS: --capath works on Linux; on macOS use --cafile /etc/ssl/cert.pem instead.
mosquitto_pub \
  -h "${MQTT_HOST}" \
  -p "${MQTT_PORT}" \
  --capath /etc/ssl/certs \
  -i "${MQTT_CLIENT_ID}" \
  -u "${MQTT_USERNAME}" \
  -P "${MQTT_PASSWORD}" \
  -t "${MQTT_TOPIC}" \
  -m "${PAYLOAD}" \
  -q 1

if [ $? -eq 0 ]; then
  echo "Data published to ${MQTT_TOPIC}"
  exit 0
else
  echo "Failed to publish"
  exit 1
fi
```

#### PowerShell

```powershell
<#
.SYNOPSIS
Raw Meal Silo - Logable MQTT Integration (PowerShell)
Device Type: silo
Device ID: <DEVICE_ID>

Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
Client ID must be unique per connection - a duplicate ID disconnects the other client.
Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.

Requirements: the M2Mqtt .NET library (M2Mqtt.Net.dll).
Install via NuGet: Install-Package M2Mqtt
(or download the package and extract lib/net45/M2Mqtt.Net.dll,
then adjust the Add-Type path below)
#>

$MqttHost = "mqtt.logable.app"
$MqttPort = 8883
$MqttUsername = "<DEVICE_ID>"
$MqttPassword = "<DEVICE_TOKEN>"
$MqttClientId = "logable-<DEVICE_ID>"
$MqttTopic = "devices/<DEVICE_ID>/data"

Add-Type -Path "M2Mqtt.Net.dll"

function Send-DeviceDataMqtt {
    $Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")

    # The "data" fields shown are this device type's registered metrics.
    # Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
    # Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
    # Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
    # Custom values appear on dashboards via a widget's "Show Custom Data" option.
    $Payload = @{
        timestamp = $Timestamp
        status = "online"
        data = @{
            weight = 18750.5
            status = 'filling'
        }
    } | ConvertTo-Json -Depth 3

    # TLS on the standard MQTT-over-TLS port; .NET validates the
    # broker's Let's Encrypt certificate against the system store.
    $Client = [uPLibrary.Networking.M2Mqtt.MqttClient]::new(
        $MqttHost, $MqttPort, $true,
        [uPLibrary.Networking.M2Mqtt.MqttSslProtocols]::TLSv1_2,
        $null, $null
    )

    try {
        $ReturnCode = $Client.Connect($MqttClientId, $MqttUsername, $MqttPassword)
        if ($ReturnCode -ne 0) {
            throw "MQTT connect failed (code $ReturnCode)"
        }

        $Client.Publish(
            $MqttTopic,
            [System.Text.Encoding]::UTF8.GetBytes($Payload),
            [uPLibrary.Networking.M2Mqtt.Messages.MqttMsgBase]::QOS_LEVEL_AT_LEAST_ONCE,
            $false
        ) | Out-Null

        Write-Host "Data published to $MqttTopic" -ForegroundColor Green
    } catch {
        Write-Error "Error: $($_.Exception.Message)"
        throw
    } finally {
        if ($Client.IsConnected) {
            $Client.Disconnect()
        }
    }
}

Send-DeviceDataMqtt
```

#### C (Paho MQTT)

```c
/*
 * Raw Meal Silo - Logable MQTT Integration (C / Eclipse Paho)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
 * Client ID must be unique per connection - a duplicate ID disconnects the other client.
 * Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
 *
 * Install: sudo apt-get install libpaho-mqtt-dev
 * Compile: gcc -o mqtt_client mqtt_client.c -lpaho-mqtt3cs
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <MQTTClient.h>

#define MQTT_ADDRESS "ssl://mqtt.logable.app:8883"
#define MQTT_CLIENT_ID "logable-<DEVICE_ID>"
#define MQTT_USERNAME "<DEVICE_ID>"
#define MQTT_PASSWORD "<DEVICE_TOKEN>"
#define MQTT_TOPIC "devices/<DEVICE_ID>/data"
#define MQTT_QOS 1
#define MQTT_TIMEOUT_MS 10000L

/* Example metric payload - CUSTOMIZE DATA HERE */
static const char *DATA_JSON =
    "{\"weight\":18750.5,\"status\":\"filling\"}";

int main(void) {
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    MQTTClient_deliveryToken token;
    char payload[1024];
    char timestamp[64];
    time_t now = time(NULL);
    int rc;

    strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));

    /* Build JSON payload - CUSTOMIZE DATA HERE */
    /*
     * The "data" fields shown are this device type's registered metrics.
     * Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
     * Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
     * Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
     * Custom values appear on dashboards via a widget's "Show Custom Data" option.
     */
    /* Metric data is passed as a %s argument (never spliced into the
     * format string), so '%' characters in values stay intact. */
    snprintf(payload, sizeof(payload),
        "{\"timestamp\": \"%s\", \"status\": \"online\", \"data\": %s}",
        timestamp, DATA_JSON);

    rc = MQTTClient_create(&client, MQTT_ADDRESS, MQTT_CLIENT_ID,
        MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (rc != MQTTCLIENT_SUCCESS) {
        fprintf(stderr, "Failed to create client (rc=%d)\n", rc);
        return 1;
    }

    conn_opts.keepAliveInterval = 60;
    conn_opts.cleansession = 1;
    conn_opts.username = MQTT_USERNAME;
    conn_opts.password = MQTT_PASSWORD;
    /* TLS: validate the broker's Let's Encrypt certificate against the
     * system CA store (works out of the box on Linux/macOS). */
    ssl_opts.enableServerCertAuth = 1;
    conn_opts.ssl = &ssl_opts;

    rc = MQTTClient_connect(client, &conn_opts);
    if (rc != MQTTCLIENT_SUCCESS) {
        fprintf(stderr, "Failed to connect (rc=%d)\n", rc);
        MQTTClient_destroy(&client);
        return 1;
    }
    printf("Connected to MQTT broker\n");

    pubmsg.payload = payload;
    pubmsg.payloadlen = (int)strlen(payload);
    pubmsg.qos = MQTT_QOS;
    pubmsg.retained = 0;

    rc = MQTTClient_publishMessage(client, MQTT_TOPIC, &pubmsg, &token);
    if (rc == MQTTCLIENT_SUCCESS) {
        rc = MQTTClient_waitForCompletion(client, token, MQTT_TIMEOUT_MS);
        printf("Data published to %s\n", MQTT_TOPIC);
    } else {
        fprintf(stderr, "Failed to publish (rc=%d)\n", rc);
    }

    MQTTClient_disconnect(client, 1000);
    MQTTClient_destroy(&client);
    return 0;
}
```

#### Arduino / ESP32

```cpp
/*
 * Raw Meal Silo - Logable MQTT Integration (Arduino / ESP32)
 * Device Type: silo
 * Device ID: <DEVICE_ID>
 *
 * Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
 * Client ID must be unique per connection - a duplicate ID disconnects the other client.
 * Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
 *
 * Libraries: WiFi, WiFiClientSecure, PubSubClient, ArduinoJson
 * Board: ESP32 or ESP8266
 */

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "mqtt.logable.app";
const int MQTT_PORT = 8883;
const char* MQTT_USERNAME = "<DEVICE_ID>";
const char* MQTT_PASSWORD = "<DEVICE_TOKEN>";
const char* MQTT_CLIENT_ID = "logable-<DEVICE_ID>";
const char* MQTT_TOPIC = "devices/<DEVICE_ID>/data";

WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);

void setupWifi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" Connected!");
}

void reconnectMqtt() {
  while (!mqttClient.connected()) {
    Serial.print("Connecting to MQTT...");
    if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
      Serial.println(" Connected!");
    } else {
      Serial.printf(" Failed (rc=%d). Retrying in 5s...\n", mqttClient.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  setupWifi();

  /* TLS: setInsecure() skips certificate validation - acceptable for a
   * quick test, but production firmware should pin the ISRG Root X1 CA
   * (Let's Encrypt root) instead:
   *
   *   static const char ISRG_ROOT_X1[] PROGMEM = R"CERT(
   *   -----BEGIN CERTIFICATE-----
   *   ...paste the ISRG Root X1 PEM from https://letsencrypt.org/certificates/
   *   -----END CERTIFICATE-----
   *   )CERT";
   *   espClient.setCACert(ISRG_ROOT_X1);
   */
  espClient.setInsecure();

  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  /* Must be set BEFORE connecting - PubSubClient's 256-byte default
   * truncates typical Logable payloads. Keep payloads under 512 bytes. */
  mqttClient.setBufferSize(512);
}

void sendMetrics() {
  if (!mqttClient.connected()) {
    reconnectMqtt();
  }
  mqttClient.loop();

  /* Build JSON payload - CUSTOMIZE DATA HERE (keep under 512 bytes) */
  JsonDocument doc;
  doc["status"] = "online";
  doc.createNestedObject("data");
  doc["data"]["weight"] = 18750.5;
  doc["data"]["status"] = "filling";
  // The "data" fields shown are this device type's registered metrics.
  // Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
  // Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
  // Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
  // Custom values appear on dashboards via a widget's "Show Custom Data" option.

  char payload[512];
  serializeJson(doc, payload, sizeof(payload));

  if (mqttClient.publish(MQTT_TOPIC, payload)) {
    Serial.println("Data published via MQTT");
  } else {
    Serial.println("MQTT publish failed");
  }
}

void loop() {
  sendMetrics();
  delay(10000); /* Send every 10 seconds */
}
```

MQTT gives no response body. Open the device's **Overview** tab to confirm the value arrived.

## If nothing appears [#if-nothing-appears]

A 401 means the token is wrong or revoked. A 400 means something in the body failed validation. See [Error codes](/docs/reference/error-codes) for the full list.

Over MQTT, a rejected message is dropped with no reply to the device. Open the device's **Connection** tab and check the **Device log** instead. It shows the rejection with the reason.

## Next [#next]

* [Send readings over HTTP](/docs/integrate/rest-api)
* [Publish readings over MQTT](/docs/integrate/mqtt)
* [Device types](/docs/reference/device-types)
