Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt

Use this file to discover all available pages before exploring further.

Letting Web Apps Send Data to MQTT

Sometimes the data you need isn’t coming from a sensor — it’s coming from a web app, a mobile app, a partner’s webhook, or a system that only speaks HTTP. Coreflux’s REST_API route can run in server mode, exposing an HTTP endpoint on the broker itself. When something POSTs to that endpoint, the route publishes the body to an MQTT topic, just like a sensor message.
Like leaving a drop box at the front door. Anyone with the right key can drop a package in, and once it’s inside, it’s handled the same way as everything else.

When to Reach for This

  • A web dashboard or mobile app needs to push commands or readings without implementing an MQTT client
  • A third-party service (Stripe, GitHub, Slack, your CRM, an IoT vendor’s cloud) supports HTTP webhooks but not MQTT
  • A legacy system can call HTTP endpoints but doesn’t speak MQTT
  • You’re behind a firewall that allows HTTP but not MQTT

The Pattern

DEFINE ROUTE WebhookIngest WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH ENABLE_SERVER "true"
        WITH SERVER_PORT '8080'
        WITH USERNAME GET ENV "WEBHOOK_USER"
        WITH PASSWORD GET SECRET "WEBHOOK_TOKEN"
        WITH ENABLE_CORS "true"
    ADD EVENT ReceiveSensorReading
        WITH ENDPOINT "/api/sensors"
        WITH METHOD "POST"
        WITH DESTINATION_TOPIC "sensors/from/http"
How it works:
  1. The route opens an HTTP server on port 8080.
  2. When something POSTs to http://your-broker:8080/api/sensors with the right Basic Auth credentials, the body of the request is published to sensors/from/http.
  3. From there, any Action subscribed to that topic processes it like a normal MQTT message — type-cast, normalize, publish into your UNS, store in the database.
A web app sending a reading would do something like:
curl -X POST http://broker.example.com:8080/api/sensors \
  -u "$WEBHOOK_USER:$WEBHOOK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sensor_id": "temp-01", "value": 25.5}'
And an Action on the broker normalizes it into the UNS:
DEFINE ACTION RouteWebhookReading
ON TOPIC "sensors/from/http" DO
    SET "sensor_id" WITH (GET JSON "sensor_id" IN PAYLOAD AS STRING)
    SET "value" WITH (GET JSON "value" IN PAYLOAD AS DOUBLE)
    PUBLISH TOPIC "acmecorp/headquarters/floor3/sensors/" + {sensor_id} + "/temperature" WITH {value}
That’s the whole pattern. The web app doesn’t need an MQTT library, the broker doesn’t need an external middleware service, and the credentials sit safely in ENV and SECRET. Use the same approach for receiving CRM events, payment notifications, AI service callbacks, or anything else the outside world wants to push to you.

Pulling Data from External APIs

The mirror image of webhook ingestion: when you need to call out to a third-party API. The same REST_API route in client mode turns an MQTT message into an outbound HTTP request, then publishes the response back to MQTT. Combined with GET ENV and GET SECRET, the credentials never appear in the route definition.
Like a vending machine. Drop the right token in (an MQTT request), and the answer pops out a slot below (an MQTT response).

When to Reach for This

Whenever a Coreflux system needs information that lives outside it: weather forecasts for HVAC control, exchange rates for billing, geocoding for a fleet, traffic data for routing, AI/ML inference, public data feeds.

The Pattern

Weather data is useful in nearly every domain — solar parks need irradiance forecasts, smart buildings adjust HVAC for outdoor temperature, fleets reroute around storms, agriculture schedules irrigation. Here’s a route that calls a weather API. The base URL and credentials come from environment variables and secrets, so the same route works in dev and prod:
DEFINE ROUTE WeatherAPI WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS GET ENV "WEATHER_API_URL"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH USERNAME GET ENV "WEATHER_API_USER"
        WITH PASSWORD GET SECRET "WEATHER_API_KEY"
        WITH REQUEST_TIMEOUT '30'
    ADD EVENT GetCurrentWeather
        WITH SOURCE_TOPIC "weather/request"
        WITH DESTINATION_TOPIC "weather/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v1/current?location={value.json.location}"
How it works:
  1. Some Action publishes a JSON message like {"location": "Lisbon"} to weather/request.
  2. The route picks it up, builds the URL with the location filled in, and fires an authenticated HTTP GET.
  3. The API responds with the weather data.
  4. The route publishes that response to weather/response.
Trigger it from any Action — say, every fifteen minutes:
DEFINE ACTION FetchWeather
ON EVERY 15 MINUTES DO
    PUBLISH TOPIC "weather/request" WITH '{"location":"Lisbon"}'
Then handle the response in another Action that subscribes to weather/response:
DEFINE ACTION StoreWeather
ON TOPIC "weather/response" DO
    SET "temp" WITH (GET JSON "temperature" IN PAYLOAD AS DOUBLE)
    SET "humidity" WITH (GET JSON "humidity" IN PAYLOAD AS DOUBLE)
    PUBLISH TOPIC "processed/weather/temperature" WITH {temp}
    PUBLISH TOPIC "processed/weather/humidity" WITH {humidity}
The HTTP credentials sit encrypted in secrets.json, the API URL sits in .env, and the Route definition stays portable across deployments. If the API key rotates, you update the secret and nothing else changes.

Live KPIs from a Database

Database routes are not just for inserting data. The same EVENT syntax supports SELECT queries — either triggered by an MQTT message or run on a fixed schedule. This is how you turn a historian into a live KPI feed: one query, repeated automatically, results published as ordinary MQTT messages that any consumer can subscribe to.
Like a live scoreboard at a stadium. The board updates every minute with fresh totals, and everyone in the stadium sees the same numbers without having to do the math themselves.

When to Reach for This

When you need rolling KPIs (averages, counts, peaks) that consumers should subscribe to like any other topic, or when you need on-demand historical lookups (last hour of readings for a device) triggered by an MQTT request. Skip it for one-off ad-hoc queries — for those, just query the database directly.

Scheduled Queries: Live Aggregates

A common need in solar operations: publish the average power output of the last 60 seconds every minute, for every inverter, with no polling code anywhere.
DEFINE ROUTE InverterKpis WITH TYPE POSTGRESQL
    ADD SQL_CONFIG
        WITH SERVER GET ENV "DB_HOST"
        WITH PORT GET ENV "DB_PORT"
        WITH DATABASE "energy_data"
        WITH USERNAME GET ENV "DB_USER"
        WITH PASSWORD GET SECRET "DB_PASSWORD"
    ADD EVENT AvgPowerLast60s
        WITH EVERY 60 SECONDS
        WITH DESTINATION_TOPIC "kpi/inverters/avg_power_60s"
        WITH QUERY "SELECT inverter_id, AVG(power_kw) AS avg_power FROM inverter_readings WHERE ts > NOW() - INTERVAL '60 seconds' GROUP BY inverter_id"
What happens here:
  1. Every 60 seconds, the route automatically runs the SQL query against your historian.
  2. The result set is serialized as JSON and published to kpi/inverters/avg_power_60s.
  3. Anyone listening — a dashboard, an Action, an AI agent — sees a fresh KPI every minute as a regular MQTT message.
No polling code, no glue script, no scheduler to maintain. The SQL is the schedule.

On-Demand Queries: Historical Lookups

For queries that should run when asked rather than on a clock, swap WITH EVERY for WITH SOURCE_TOPIC:
    ADD EVENT GetInverterHistory
        WITH SOURCE_TOPIC "kpi/request/history"
        WITH DESTINATION_TOPIC "kpi/result/history"
        WITH QUERY "SELECT ts, power_kw FROM inverter_readings WHERE inverter_id = '{payload}' AND ts > NOW() - INTERVAL '1 hour' ORDER BY ts"
Now publishing inv-01 to kpi/request/history returns the last hour of readings on kpi/result/history. Operators, AI agents, or other systems can pull historical context on demand. The pattern at a glance: raw data lives in the database, KPIs and historical lookups are exposed as MQTT topics. Whether you’re tracking solar inverters, traffic light timings, or refrigerated truck temperatures, your dashboards and downstream consumers don’t care where the data physically lives — they just subscribe to topics like everything else.

Keeping Credentials Out of Your Code

Never hardcode database passwords, API keys, or broker credentials in your code. Coreflux gives you two runtime stores — one for ordinary configuration, one for secrets that need to be encrypted.
Like the difference between leaving your keys taped to the front door versus keeping them in a safe. ENV is the wallet you carry openly (which city, which port, which feature flag). Secrets is the safe you only open when you need it (passwords, API keys, tokens).

When to Reach for This

The first time you deploy outside your laptop — or earlier if you’re committing your notebook to git from day one. Get the habit before you have any real credentials in flight; retrofitting GET SECRET into a working system is more painful than starting with it.

The Two Operations

  • GET ENV "NAME" reads from a managed .env file. Use it for non-sensitive configuration: hostnames, ports, region tags, feature flags. Safe to log.
  • GET SECRET "NAME" reads from secrets.json, which is encrypted at rest with AES-256-GCM. Use it for anything sensitive: passwords, API keys, tokens. Never logged.
Both can be referenced directly inside Route configurations, which is exactly where you need them most:
DEFINE ROUTE ProductionDB WITH TYPE POSTGRESQL
    ADD SQL_CONFIG
        WITH SERVER GET ENV "DB_HOST"
        WITH PORT GET ENV "DB_PORT"
        WITH DATABASE GET ENV "DB_NAME"
        WITH USERNAME GET ENV "DB_USER"
        WITH PASSWORD GET SECRET "DB_PASSWORD"
        WITH USE_SSL "true"
    ADD EVENT StoreSensorReading
        WITH SOURCE_TOPIC "sensors/+/reading"
        WITH QUERY "INSERT INTO readings (ts, sensor_id, value) VALUES (NOW(), '{sensor_id}', '{value.json}')"
The same Route definition now works in development, staging, and production — only the .env file and secrets.json differ between environments. To register a secret, run this once from a notebook or the broker console:
KEEP SECRET "DB_PASSWORD" WITH "your-actual-password"
After that, the password lives encrypted on the broker. Your route definitions, your git history, your AI assistant transcripts, and your logs all stay clean.

Next Steps

REST API Routes

Client and server mode, endpoints, and authentication.

Environment Variables and Secrets

.env, secrets.json, and secure deployment.
Last modified on May 20, 2026