A Sparkplug B payload is a Protobuf-encoded message that carries a timestamp, a sequence number and a list of typed metrics. By the end of this guide you will know what each field means, why the raw bytes look unreadable in an ordinary MQTT client, and how to inspect payloads quickly when you are commissioning or debugging an edge node.
Why Sparkplug B Payloads Look Like Gibberish
If you subscribe to spBv1.0/# with a generic tool such as mosquitto_sub, you will see a mix of readable tag names surrounded by strange characters. That is not corruption. Sparkplug B uses Google Protocol Buffers, a binary serialization format, so field numbers, lengths and numeric values are written as compact varints and fixed-width bytes rather than text.
The trade-off is deliberate. Industrial networks often run over cellular links, satellite backhaul or congested plant Ethernet, and a gateway may publish thousands of tags. A binary encoding keeps messages small, preserves exact numeric types (a 64-bit unsigned counter stays a 64-bit unsigned counter) and gives every consumer the same schema. The cost is that you need the schema, the sparkplug_b.proto file, to turn the bytes back into something human readable.
Try it yourself with a generic subscriber:
mosquitto_sub -h broker.connectmq.com -p 1883 -i payload-viewer-7f3a -t "spBv1.0/demo-7f3a/#" -v
Anything published by a Sparkplug edge node into that group will print as partially binary text. To make sense of it, you need to understand the structure underneath.
The Sparkplug B Payload Structure
The schema lives in the Eclipse Tahu project as sparkplug_b.proto. At the top level there is a single Payload message:
message Payload {
optional uint64 timestamp = 1; // ms since Unix epoch, UTC
repeated Metric metrics = 2; // the actual data
optional uint64 seq = 3; // 0-255, wraps around
optional string uuid = 4; // optional schema identifier
optional bytes body = 5; // optional opaque bytes
}
| Field | Type | Purpose | Notes |
|---|---|---|---|
| timestamp | uint64 | Time the payload was created | Milliseconds since epoch in UTC |
| metrics | repeated Metric | The tag values being reported | Can contain one metric or thousands |
| seq | uint64 | Message sequence number | 0 to 255, then wraps to 0 |
| uuid | string | Optional identifier for the payload schema | Rarely used in practice |
| body | bytes | Optional application-specific bytes | Not interpreted by Sparkplug |
The seq field is what lets a host application detect lost messages. The NBIRTH starts at 0 and each subsequent message from the same edge node increments it by one. A gap means something was missed, which usually triggers a rebirth request. That recovery flow is called a rebirth.
Inside a Metric
Most of the interesting data sits in the Metric message. Each metric is a single named value with optional metadata:
message Metric {
optional string name = 1;
optional uint64 alias = 2;
optional uint64 timestamp = 3;
optional uint32 datatype = 4;
optional bool is_historical = 5;
optional bool is_transient = 6;
optional bool is_null = 7;
optional MetaData metadata = 8;
optional PropertySet properties = 9;
oneof value {
uint32 int_value = 10;
uint64 long_value = 11;
float float_value = 12;
double double_value = 13;
bool boolean_value = 14;
string string_value = 15;
bytes bytes_value = 16;
DataSet dataset_value = 17;
Template template_value = 18;
MetricValueExtension extension_value = 19;
}
}
A few points that trip people up:
- name versus alias. Birth messages must include the full metric name, such as
Line1/Spindle/Speed. If the edge node assigns an alias, later NDATA and DDATA messages can send only the numeric alias to save bytes. A decoder that did not see the birth cannot map the alias back to a name. - datatype is a number. The
datatypefield is an integer code such as 3 for Int32, 9 for Float or 12 for String. It tells you how to interpret the value slot. The full list is in our Sparkplug B data types reference. - The value slot depends on the type. Int8 through UInt32 travel in
int_value, Int64 and UInt64 and DateTime inlong_value. Signed values in an unsigned field are encoded as two's complement, so a decoder has to look atdatatypeto recover a negative number correctly. - is_null is explicit. A metric can exist with no current value. Instead of sending zero, the node sets
is_nullto true, which is important for quality-aware consumers. - is_historical and is_transient. Historical metrics carry backfilled data that should not overwrite the live value. Transient metrics should be displayed but not stored by a historian.
A Decoded Example
Here is what a typical DDATA payload from a CNC machine looks like after decoding, rendered as JSON for readability:
{
"timestamp": 1792137600123,
"seq": 42,
"metrics": [
{ "alias": 3, "timestamp": 1792137600120, "datatype": 9, "floatValue": 1487.5 },
{ "alias": 4, "timestamp": 1792137600120, "datatype": 11, "booleanValue": true },
{ "alias": 7, "timestamp": 1792137600121, "datatype": 3, "intValue": 4294967291 }
]
}
Notice two details. First, the metrics have aliases but no names, so you need the DBIRTH to know that alias 3 is spindle speed. Second, the Int32 metric shows 4294967291 in the raw unsigned field. Interpreted as a signed 32-bit integer it is -5. This is exactly the kind of thing a naive decoder gets wrong.
The matching DBIRTH would contain the full definitions:
{
"timestamp": 1792137590000,
"seq": 1,
"metrics": [
{ "name": "Spindle/Speed", "alias": 3, "datatype": 9, "floatValue": 0.0 },
{ "name": "Spindle/Running", "alias": 4, "datatype": 11, "booleanValue": false },
{ "name": "Axis/X/Offset", "alias": 7, "datatype": 3, "intValue": 0 }
]
}
Properties, DataSets and Templates
Beyond simple scalars, the Sparkplug B payload can carry richer structures:
- PropertySet. A list of keys and typed values attached to a metric. The well-known
Qualityproperty uses 192 for good, 0 for bad and 500 for stale. Engineering units, ranges and descriptions can also be attached as properties. - DataSet. A small table with column names, column types and rows. Useful for recipe parameters or a batch of readings.
- Template. A user-defined type, similar to a UDT in a PLC. A template definition is declared once in the NBIRTH and instances reference it. Templates are how Sparkplug models pumps, motors and other repeated equipment.
- MetaData. Information for bytes and file metrics such as content type, size, MD5 and file name.
Most production systems use scalars with a Quality property. DataSets and templates are powerful but add complexity, so introduce them only when a flat list of metrics becomes hard to manage.
Reading Sparkplug B Payloads in Practice
You have three practical options for turning binary Sparkplug data into something you can read.
Decode with your own code
Compile sparkplug_b.proto for your language and parse each message. This is the right choice for production consumers, and it is only a few lines in Python. Our tutorial on how to decode Sparkplug B in Python walks through a complete subscriber, including alias resolution and signed integer handling.
Use the Tahu reference libraries
The Eclipse Tahu project provides the canonical proto file and reference implementations in several languages. They are a good starting point when you are building an edge node or a host application and want behaviour that matches the specification.
Use a broker that decodes for you
When you are commissioning, you do not want to write code just to check whether a tag is publishing. ConnectMQ detects Sparkplug B topics, decodes Protobuf (and JSON-encoded Sparkplug) payloads and shows them in readable form in the portal topic explorer. It can also republish Sparkplug data into ISA-95 topics, so spBv1.0/factory1/DDATA/line1/cnc1 becomes available as connectmq/factory1/production/line1/cnc1/telemetry for tools that do not speak Protobuf. If you are hosting your own broker, you would need a separate decoding service to get the same view.
Common Payload Pitfalls
| Symptom | Likely cause | Fix |
|---|---|---|
| Metrics show numbers instead of names | Decoder missed the birth message | Request a rebirth or start the consumer before the node |
| Negative values appear as huge numbers | Signed type read from unsigned field | Apply two's complement based on datatype |
| Timestamps look like the year 50000 | Seconds versus milliseconds mix-up | Sparkplug timestamps are always milliseconds |
| DecodeError in the parser | Payload is not Sparkplug B, or wrong proto version | Check the topic starts with spBv1.0 and use the Tahu proto |
| Seq jumps unexpectedly | Lost messages or two nodes with the same ID | Ensure unique edge node IDs and use QoS appropriately |
Most of these show up during commissioning. Keeping a decoded live view open while you bring up a new gateway saves a lot of guesswork.
How the Payload Fits the Bigger Picture
The payload is only half of Sparkplug. The other half is the Sparkplug topic namespace and the state rules built around birth and death certificates. Together they make data self-describing and state-aware, which is why Sparkplug is popular as the transport for a Unified Namespace. For the architectural view, read MQTT, Sparkplug B and the Unified Namespace and our pillar guide to Industrial IoT with MQTT.
Any standards-compliant MQTT broker can carry Sparkplug B, because the broker never needs to parse the payload to route it. What a Sparkplug-aware broker adds is visibility: decoded views, readable republishing and topic structure detection that make the binary data approachable for the whole team.
Get Started with ConnectMQ
Point a Sparkplug edge node or simulator at the public MQTT test broker using a unique group ID, then move to your own fully managed MQTT service when you need credentials and isolation. Create a free Serverless broker at app.connectmq.com, no credit card required, and watch your Sparkplug B payloads decoded live in the ConnectMQ topic explorer.
Frequently Asked Questions
Is a Sparkplug B payload always Protobuf?
The specification defines Protobuf as the encoding for Sparkplug B. Some tools also produce a JSON representation of the same structure for debugging or bridging, and ConnectMQ can decode both forms.
Why do my metrics have aliases but no names?
The edge node sent full names only in its birth certificate and uses numeric aliases afterwards to save bandwidth. Your consumer must store the name-to-alias mapping from the NBIRTH or DBIRTH, or request a rebirth to get it again.
What is the maximum size of a Sparkplug B payload?
Sparkplug itself does not set a small limit, but your broker does. On the ConnectMQ public broker messages are limited to 64 KB, and Serverless plans allow 256 KB (free) or 1 MB (paid).
Do I need the proto file to read Sparkplug data?
Yes, unless a tool decodes it for you. The sparkplug_b.proto file from Eclipse Tahu is the canonical schema, and every Sparkplug decoder is built on it.



