Guides

Decode the payload

Turn raw bytes into named readings your rules and sinks can use.

A device sends 00018d460000300040e12402. Your billing system wants index: 18.061. A decoder is what happens in between.

Decoding is worth doing early: rules filter on decoded fields, and the custom output editor can only offer you fields it has seen a decoder produce.

Where a decoder lives

A decoder attaches to a device profile's technology, not to a device. Define it once for the model and every device on that profile decodes the same way.

Where: Profiles → Decoders to write one, then attach it on the profile's technology. Or Flow → Decoders, which is the same thing from the pipeline view.

Pick a kind

Grammar
Describe the payload's fields declaratively — offsets, widths, types, scaling. No code. The right default.
JavaScript
For payloads a grammar cannot express: conditional layouts, bit-packing that varies by message type, lookup tables.
None
Forward the payload untouched. Correct when the receiving system already understands the device's raw format.

Prefer grammar when it fits. It cannot loop forever, cannot throw in surprising ways, and reads as documentation of the payload format.

The two output groups

A decoder returns readings in two groups, and the distinction carries all the way to your sink:

{
  "telemetry": { "index": 18.061, "unit": "m3", "battery": 100 },
  "meta": { "seqNumber": 18, "rssi": -103.41 }
}

telemetry is what the device measured. meta is about the transmission. Put things in the right group — some output shapes deliver only one of them, and a reading filed under meta will go missing from a sink expecting telemetry.

The JavaScript sandbox

JavaScript decoders run in an isolated V8 sandbox:

  • No network, no filesystem. A decoder cannot call out.
  • Memory and time limited. A runaway script fails that one message; the pipeline keeps running.
  • TypeScript syntax is accepted; types are stripped before execution.
The sandbox can be switched off
If decoder execution is disabled for the deployment, JavaScript decoders are stored but not run, and payloads are forwarded raw. If a JS decoder appears to do nothing at all on an otherwise healthy pipeline, check this with whoever operates the Center.

When decoding fails

Decoding is best-effort. A decoder that throws, a profile with no decoder, or a callback carrying no payload at all does not fail the message — it continues with whatever it had.

That is deliberate: losing a reading is worse than delivering it undecoded. But it means a sink can see two different shapes for the same device.

Check payload.decodeError on a message that looks wrong. It is the difference between "the decoder is broken" and "this callback never had a payload to decode".

Not every Sigfox callback carries data
Only DATA_UPLINK, DATA_BIDIR and SERVICE_DATA_ADVANCED contain a payload frame. Status, acknowledge, repeater and error callbacks have nothing to decode and arrive as they came — that is not a decoder fault. Filter them out with a rule if your sink should not see them.

Decryption

If the profile defines encryption, the payload is decrypted before decoding, using the key on the device's binding. Keys are encrypted at rest and never shown again — leave the field blank when editing to keep the stored value.

Test it

The editor has a Test with a payload panel. Paste the bytes a device would send as hex, set the message type and any other input fields the callback would carry (device, seqNumber, rssi, ack…), and press Run. The script runs on the server through the same codec ingest uses — never a copy in the browser — so what comes back is what a real message would produce: telemetry, meta, the error if it threw, and the list of fields Routing would offer.

The most common surprise shows up here in one click: a payload of the wrong length. A decoder that checks bytes.length and returns only meta.error produces no telemetry, and a sink wired to it receives only that error — exactly what the panel shows before you find out from the sink.

Use as routing sample keeps the run as the decoder's recorded output shape and sample. That is the same record ingest writes after the first real message, written before it: open Flow → Routing for a pair using this source and the field picker lists these fields, the preview shows these values — rendered by the code that performs delivery, so what you see is what the sink receives. If the script changed since it was saved, the button saves it first; the sample must come from the script that will actually run.

A real uplink later refreshes the record with what the device actually sent.

A decoder is one half of a codec. The same row can carry a second function, encodeDownlink, for when the Center has to answer the device. It receives commands — { name: 'valveOpen', args: { value: false } } — from a rule (and later from the device's queue), plus the uplink being answered, and returns the one frame to send: { bytes } or { hex }, exactly 8 bytes on Sigfox, whole bytes on LoRaWAN.

Open the codec, switch Decoder → Encoder, turn on This codec has an encoder, and write it against the ambient types the editor loads. The Test with commands panel runs it on the server with the commands and uplink you describe and shows the frame byte by byte. The rule that uses it is described in Respond with a downlink.

Next

Choose what the sink receives
Now that you have fields, decide which of them are delivered.
Filter with rules
Act on decoded values — drop, route, or alert.
Copyright © 2026