How an integrated application uses its QwantiFi MQTT credentials to talk to one device or many devices once those credentials have been distributed.
This guide is client-agnostic: it describes the MQTT concepts and message flow with pseudocode, so it applies to any MQTT client library (mqtt.js, MQTTnet, Paho, Eclipse Mosquitto clients, etc.).
A single integrated application credential lets one integrated application instance command every device it owns β there is no per-device credential to manage on the integrated application side.
A device has exactly one subscribe topic and one publish topic. It listens only for commands addressed to its own serial number and has no routing logic of its own.
A POS holds lists of publish and subscribe topics covering all the devices under its scope (a whole reseller, or a single merchant). It addresses each device by publishing to that device's topic and listens for each device's responses on a separate topic.
Commands and responses travel on different topic branches so a device never re-receives its own output and the integrated application is not fighting echo/recursion.
The mqttConfig object you were given at onboarding carries everything you need:
Example of a merchant-scoped credential (a reseller-wide credential looks the same but its username is pos-{reseller} and its topics span every merchant under that reseller). The scope is fixed at issue time β the integrated application does not select it:
The JWT is the password. Do not also send a separate password β the broker validates the JWT signature to authenticate you, then applies your authorization rules from its broker-side store, matched by your username.
Transport: trust port, not operatingMode. operatingMode is a coarse hint and is currently reported as mqtts regardless of environment. Decide TLS from the port you were issued and from what your onboarding contact told you the environment uses; if peerCommonName is populated, verify the server certificate against it.
Publish grant (what you may use): MagTek/13204/2026213/+/SendCommand
Actual command you publish (one device): MagTek/13204/2026213/TEST-001/SendCommand
Subscribe grant (what you may use): MagTek/13204/2026213/device/publish/+/MMSMessage and β¦/+/Status
The grant is leaf-specific: publishing is allowed only on the SendCommand leaf, and subscribing only on the MMSMessage and Status leaves β a broad β¦/{sn}/# will not match the grant and is denied. {% endhint %}
Open a standard MQTT connection using the credential fields:
Multiple POS terminals (lanes, stations) for the same merchant connect with the same username. The broker requires a unique clientId per live connection β if two clients use the same clientId, the broker disconnects the earlier one. Always append a per-terminal/per-process suffix:
The username is a safe shared identity; the clientId is what must be unique.
All topics follow the hierarchy:
Two distinct branches are used:
The device is authorized for its whole subtree MagTek/{r}/{c}/{sn}/# and listens on the concrete β¦/{sn}/SendCommand leaf β the command topic your POS credential is authorized to publish to.
The device publishes its output to the separate β¦/device/publish/{sn}/β¦ branch β never back into its own command subtree β which is why command and response do not loop. It emits two event kinds: MMSMessage (transaction/message data) and Status.
To command one device with serial TEST-001 (reseller 13204, customer 2026213):
Subscribe for its responses (do this before sending, so you don't miss the reply). A device emits two event kinds, so subscribe to both leaves:
Publish a command to that device's command topic:
Handle the response on your message callback:
You have two equivalent strategies β choose per your routing needs.
Subscribe once per event leaf to the scope wildcard and receive every device's responses on a single handler:
You do not have to build those strings yourself β they are the last two entries of mqttConfig.subscribeTopics, already correct for your scope:
This is the recommended form for anything beyond a handful of devices: two subscriptions total, and devices onboarded later are picked up automatically with no resubscribe. Extract the device serial from the topic to know who replied:
To send, publish to each device's command topic:
Subscribe to each device's two response leaves individually β these all fall under your authorized scope wildcard, so the broker permits them:
Useful when different devices feed different handlers, or when you want the subscription set to mirror exactly the devices you manage.
Your credential's topic lists enumerate the devices provisioned to your scope at the moment it was issued β a convenient starting address book. Because authorization is a scope wildcard, you are not limited to that snapshot: devices added after you connected are reachable immediately by addressing the new serial (no new credential needed).
The cleanest way to enumerate them at runtime is , which returns a roster β one entry per device, each already pairing the serial with its topics β so you never parse serials out of topic paths. Alternatives: your own provisioning records, or re-fetching the flat lists via or .
Before sending to a device you have not heard from in a while, you can ask whether it currently holds a broker session, rather than publishing and waiting on silence β use . It reports connected, status (PENDING/PROVISIONED), and blocked.
MQTT is asynchronous β a response arrives as a separate message. To match a reply to the command and device that produced it:
By device: the response topic contains the device serial, so the topic itself tells you which device replied and which event kind it is.
By command: if you issue several commands to the same device, include a correlation token in your command payload (per the device protocol) and echo it in the response, or serialize commands per device.
Keep a per-device in-flight map keyed by serial (and correlation token if available) so concurrent operations across many devices do not get cross-wired.
The JWT in password is short-lived β 8 hours by default, though configurable per environment, so always drive your timer off expiresAt rather than a hard-coded 8. You were also handed a long-lived, opaque refresh token once, at onboarding (store it securely) β it is what lets the integrated application mint fresh JWTs on its own, with no portal involvement.
Refresh ahead of expiry. Before expiresAt, mint a fresh JWT (same scope, same username) with =, passing your username + refresh token. A good practice is to refresh at ~75β80% of the lifetime. This re-mints the JWT without rotating the refresh token.
Don't let the JWT lapse β the lookup endpoints require a live one. pos-topics-list, pos-device-list, and pos-device-status
For the full credential model behind refresh, see the .
Clean vs persistent session. A clean session means you must re-subscribe on every reconnect. A persistent session (clean=false with a stable clientId) lets the broker queue messages and retain subscriptions across brief disconnects. The clientId still has to be unique across concurrently live terminals.
Multiple terminals. Same username, different clientId each. They share the identity, JWT, refresh token, and authorization scope, so each terminal is authorized for every device under the scope. Partitioning devices across lanes is a client-side concern.
Payload framing is device-defined. The MQTT platform moves opaque bytes; the SendCommand
Scope examples use reseller 13204, customer 2026213, device TEST-001.
For the complete endpoint surface β request shapes, headers, and status codes β see the .
Connect checklist
Actual response topics (one device): MagTek/13204/2026213/device/publish/TEST-001/MMSMessage and β¦/TEST-001/Status
401pos-credentials/refreshReconnect with the new token. The broker checks the JWT at CONNECT time. An existing connection is not retroactively killed at the instant of expiry, but any reconnect requires a still-valid token.
Re-subscribe after reconnect unless you use a persistent session (see below).
Security. Treat the JWT like a password β never log it. Use TLS wherever the environment offers it. Both the JWT and the refresh token are bearer secrets.
broker
Broker host to connect to. Environment-specific β always take it from this field rather than hard-coding it.
port
Broker port (1883 plain, 8883 TLS are the usual choices). Environment-specific, like broker.
peerCommonName
{
"broker": "<your-broker-host>",
"port": 8883,
"peerCommonName": "",
"username": "pos-13204-2026213",
"password": "<HS256 JWT>",
"publishTopics": ["MagTek/13204/2026213/TEST-001/SendCommand"],
"subscribeTopics": [
"MagTek/13204/2026213/device/publish/TEST-001/MMSMessage",
"MagTek/13204/2026213/device/publish/TEST-001/Status",
"MagTek/13204/2026213/device/publish/+/MMSMessage",
"MagTek/13204/2026213/device/publish/+/Status"
],
"operatingMode": "mqtts",
"expiresAt": "2026-06-19T01:43:00Z"
}client = mqtt.connect({
host: mqttConfig.broker,
port: mqttConfig.port,
username: mqttConfig.username, // CONNECT username
password: mqttConfig.password, // CONNECT password = the JWT
clientId: makeUniqueClientId(mqttConfig.username),
tls: port_is_tls, // when port is a TLS port
expectedCommonName: mqttConfig.peerCommonName // verify against server cert
})makeUniqueClientId(username) = username + "-" + stationId + "-" + randomShortId()
// e.g. "pos-13204-2026213-lane1-9fa3"MagTek / {ResellerId} / {CustomerId} / {DeviceSN}Command (to device)
MagTek/{r}/{c}/{sn}/SendCommand
POS (and backend)
the device
client.subscribe("MagTek/13204/2026213/device/publish/TEST-001/MMSMessage", qos=1)
client.subscribe("MagTek/13204/2026213/device/publish/TEST-001/Status", qos=1)topic = "MagTek/13204/2026213/TEST-001/SendCommand"
payload = buildDeviceCommand(...) // device-protocol bytes β see note below
client.publish(topic, payload, qos=1)on_message(topic, payload):
if topic.startsWith("MagTek/13204/2026213/device/publish/TEST-001/"):
handleDeviceResponse("TEST-001", topic, payload)client.subscribe("MagTek/13204/2026213/device/publish/+/MMSMessage", qos=1) // merchant scope
client.subscribe("MagTek/13204/2026213/device/publish/+/Status", qos=1)
// reseller scope would be: "MagTek/13204/+/device/publish/+/MMSMessage" (and .../+/Status)filters = mqttConfig.subscribeTopics.slice(-2) // [ .../+/MMSMessage, .../+/Status ]
for topic in filters:
client.subscribe(topic, qos=1)on_message(topic, payload):
// topic = ".../device/publish/<SN>/<MMSMessage|Status>"
parts = topic.split("/")
sn = parts[parts.length - 2]
handleDeviceResponse(sn, topic, payload)for sn in mySerials:
client.publish("MagTek/13204/2026213/" + sn + "/SendCommand", buildCommand(sn), qos=1)for sn in mySerials:
client.subscribe("MagTek/13204/2026213/device/publish/" + sn + "/MMSMessage", qos=1)
client.subscribe("MagTek/13204/2026213/device/publish/" + sn + "/Status", qos=1)loop:
if now() > expiresAt - refreshMargin:
mqttConfig = POST /api/pos-credentials/refresh { username, refreshToken }
reconnectWith(mqttConfig)Send command to one device
MagTek/13204/2026213/TEST-001/SendCommand (publish)
Receive one device's responses
MagTek/13204/2026213/device/publish/TEST-001/MMSMessage and β¦/TEST-001/Status (subscribe)
Receive all merchant devices' responses
Expected TLS certificate common name (when using TLS). May be empty.
username
MQTT username β present in the CONNECT username field. Shared by all integrated application terminals in the same scope.
password
A short-lived HS256 JWT β present in the CONNECT password field. Used for authentication (signature + expiry); the authorization rules live broker-side, not in the token.
publishTopics
List of explicit per-device command topics β one β¦/{sn}/SendCommand for every device under your scope. Informational (your real grant is a broker-side scope filter). Empty when no devices are onboarded. No wildcard entry: MQTT forbids wildcards in a PUBLISH topic.
subscribeTopics
List of per-device response topics β for each device, its β¦/device/publish/{sn}/MMSMessage and β¦/device/publish/{sn}/Status β followed, last, by the two scope filters β¦/device/publish/+/MMSMessage and β¦/+/Status. Subscribe to that trailing pair to cover every device in your scope at once.
operatingMode
Transport hint (mqtts). Treat port and your TLS settings as authoritative β see the transport note below.
expiresAt
UTC instant the JWT stops being valid. Refresh before this.
Response (from device)
MagTek/{r}/{c}/device/publish/{sn}/MMSMessage and β¦/{sn}/Status
the device
POS (and dashboards)
MagTek/13204/2026213/device/publish/+/MMSMessage and β¦/+/Status (subscribe)
Receive all reseller devices' responses
MagTek/13204/+/device/publish/+/MMSMessage and β¦/+/Status (subscribe)
Command-scope grant (merchant)
MagTek/13204/2026213/+/SendCommand (broker rule, not sendable)
