All pages
Powered by GitBook
1 of 1

Loading...

POS Integration Guide

Managing Devices with MQTT Credentials

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.).

Prerequisite: you already hold your integrated application credentials.

Credentials are provisioned for you. They're issued by MagTek and handed to you during onboarding. There is no self-service onboarding endpoint. What you receive is an mqttConfig object plus a one-time refresh token; from there everything you need is self-service (see Token lifecycle and reconnection, below). If you have not received credentials, or need them re-issued, contact whoever onboarded you. For the model behind these credentials, see the .

The Big Picture

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:

Field
Meaning

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:

Direction
Topic shape
Who publishes
Who subscribes
  • 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):

  1. 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:

  1. Publish a command to that device's command topic:

  1. 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.

Action
Topic

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

reject an expired JWT with a
401
. Only
pos-credentials/refresh
still accepts one, precisely so an expired integrated application can recover.
  • Reconnect 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).

  • leaf and binary layout come from the device command protocol, not this platform.
  • 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

    The Credential Payload

    Merchant-scoped Credential Example (JSON)

    {Two ways to read subscribeTopics. The leading entries are explicit, per-device convenience topics β€” one pair per device provisioned to your scope right now, growing and shrinking as devices come and go. The last two entries are the scope filters: subscribe to those instead and you receive from every device in your scope in two SUBSCRIBEs, including devices onboarded after this call. Pick per-device when each device needs its own handler; pick the filters when you would otherwise be managing a subscription list. publishTopics has no such pair β€” MQTT forbids wildcards in a PUBLISH topic, so commands always go out one device at a time.

    Don't rely on device ordering. Two things are guaranteed: within a device's pair MMSMessage comes before Status, and the two scope filters are always the last two entries of subscribeTopics. The order the devices themselves appear in is not stable between calls, so never pair the lists positionally. When you need serial-to-topic pairing, use , which returns them already associated.

    Authorization scope vs. target topic. Your broker-side grant is a scope filter with a + at the device-serial position β€” the boundary of what you are allowed to use β€” covering every device (present and future) under your scope. You target one device by publishing/subscribing to its concrete topic, which the broker permits because it falls inside the filter:

    Connecting to the Broker

    ClientId Must Be Unique Per Connection

    Understanding the Topic Model

    Managing a Single Device

    Payload framing: the SendCommand leaf and the exact binary framing of payload are defined by the device protocol, not by the QwantiFi platform. Use the device's command spec β€” see the to build/parse the bytes. Treat the payload as opaque at the MQTT layer.

    Managing Many Devices

    Wildcard subscriptions (simplest)

    Per-device Subscriptions (Explicit)

    Discovering Your Device Serials

    Checking Whether a Device is Reachable

    connected: false is not proof the device is off. It is also what you get when the broker's management API is unreachable from the platform. Treat it as "not known to be connected" β€” good for surfacing "device may be offline" to an operator, not as a hard gate that refuses to send.

    Correlating Responses to Requests

    Token Lifecycle and Reconnection

    Operational Notes

    Quick Reference

    POST /api/pos-device-list
    POST /api/pos-credentials/refresh
    POST /api/pos-topics-lis
    POST /api/pos-device-status/{deviceSn}
    POST /api/pos-credentials/refresh
    QwantiFi Credential Model
    API Reference
    QwantiFi Credential Model

    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)

    POST /api/pos-device-list
    DynaFlex Programmer's Manual