zplCloud Blog

Print Labels with zplCloud and Azure Service Bus - ZPL or with JSON Payloads

Two payload shapes, one queue: a JSON record that fills a design, or raw ZPL in an envelope. PeekLock, so nothing is lost and nothing prints twice.

6 min read zplCloud Team

The queue is already the trigger

Azure Service Bus queue with JSON records and raw ZPL printed as labels on a Zebra printer

If your systems already talk over Azure Service Bus, the print job is a message like any other. A Data Streaming subscription of type Service Bus consumes a queue or a topic subscription and prints every message - no polling job, no print middleware, no Windows print server between the bus and the printer.

The interesting part is that a message can be either of two things, and zplCloud decides per message:

  • A JSON record - the object keys fill the bindings of a label design. Use this when the sender knows the data, not the layout.
  • A ZPL envelope - a JSON object carrying finished ZPL. Use this when the sender already produces ZPL and just needs it on a printer.

Both go through the same subscription; you do not configure a mode.

Setting up the subscription

FieldValue
TypeService Bus
Connection configthe namespace connection string
Topicqueue name, or topic/Subscriptions/subscriptionName
Label designthe design used for JSON records
Printerthe default target printer

The connection string is stored AES-encrypted at rest.

Queue or topic is decided by the Topic field, by convention: if the value contains /Subscriptions/, it is treated as topic/Subscriptions/subscriptionName; otherwise it is a queue name. You can also leave it empty and put EntityPath=printer in the connection string.

Subscriptions are not created automatically. Create the topic subscription once in the portal, Service Bus Explorer or the emulator (topic.1/Subscriptions/subscription.1); zplCloud only consumes it.

Unlike the Kafka integration, Service Bus runs cloud-side only - the consumer lives in the backend and connects outbound. There is no on-prem agent variant, because a Service Bus namespace is reachable from the internet anyway.

Payload A - a JSON record for a design

{ "ean": "4006381333930", "qty": 2, "dest": "Ramp 4" }

Keys are matched to the design's bindings by name. Keys without a binding are ignored, bindings without a key render empty.

If the body is not JSON at all, the whole text is bound as $message - enough for a single-field label.

A render error names the field that caused it rather than handing you an SDK message:

ZPL-Renderfehler bei qty: Wert 'zwei' → ...

That is usually a type or length problem: a numeric field fed a word, or a value longer than the field can hold.

Payload B - raw ZPL in an envelope

When the sender already has ZPL, wrap it:

{
  "schemaVersion": 1,
  "zpl": "^XA^FO50,50^A0N,40,40^FDFrom the bus^FS^XZ",
  "printer": "*"
}

The rules for this being treated as an envelope are deliberately strict:

  • the body must be a JSON object,
  • it must have a zpl property that is a non-empty string,
  • and it must additionally carry printer (string) or schemaVersion (number).

The last condition is the important one. A plain data record that happens to contain a field called zpl is not hijacked and sent to the printer raw - it still goes through the design path. If you want the envelope behaviour, say so explicitly with printer or schemaVersion.

In envelope mode the design is not touched at all: the ZPL goes to the printer byte for byte.

Choosing the printer per message

The envelope's printer overrides the subscription's printer for that message. Two sentinels broadcast:

  • "*" or "all" - every printer in the workspace, Weblink and remote agent printers alike.

Each target counts as its own label against your quota, so a broadcast to eight printers is eight labels. That is the right model for a shift-change notice; it is an expensive mistake for a per-order label.

Delivery semantics: PeekLock, and what happens when a print fails

This is where a queue integration is either trustworthy or not. The receiver runs in PeekLock mode with PrefetchCount = 0 - receiving is not deleting, and no messages are buffered inside the process, so stopping or restarting the subscription never swallows or duplicates a batch.

OutcomeWhat happens to the message
PrintedComplete - the message leaves the bus
Print failed, delivery < 3Abandon - redelivered, then a 10 s backoff before the next receive
Print failed, delivery ≥ 3Dead-lettered with reason Print-Fehler (MaxDeliveryCount erreicht) and the error as description

Two consequences worth stating plainly:

  • A print failure does not stop the consumer. Unlike the Kafka path, the subscription keeps running and retries; only the individual message escalates to the dead-letter queue.
  • Success is "the bytes reached the printer socket." A Zebra never acknowledges a ZPL label, so waiting for a confirmation would mean a 20-second stall and a duplicate print. Complete happens once the send succeeded - the debug column shows sent (no response) for exactly this case.

Because failed messages are dead-lettered rather than retried forever, the DLQ becomes your queue of labels that did not come out. That is a list you can act on, which is the entire point.

Idle keepalive

If nothing arrives for 60 seconds, the subscription sends a wake command to the target printer. Link-OS printers drop into a low-power state, and without this the first message after a quiet period pays the wake-up latency. The keepalive runs fire-and-forget so it cannot block the receive loop and cause a pile-up.

What you see while it runs

Each subscription keeps a ring buffer of the last 50 messages with the value preview, the print result, and an end-to-end trace:

bus=12ms design=3ms zpl=18ms print=64ms total=85ms

bus is the time between enqueue and receive - that is the queue's latency, not yours. The debug column additionally records the printer, the ZPL byte count, the HTTP status and the raw response.

Messages that fail are also written to a persistent error log, but only on their first delivery. Without that guard, Service Bus's own retries would file the same bad message three times.

Quotas

Every streamed label counts as a render against the plan of the subscription's owner. Two distinct messages appear when you run out:

  • Render-Limit erreicht (…/… Labels diesen Monat, Tarif …) - the monthly render budget,
  • Streaming-Kontingent erreicht (…) - the streaming cap specifically.

Starter includes streaming for testing, capped at 100 streamed labels per month. Pro includes 2 streaming endpoints (Kafka or Service Bus); each additional endpoint costs 10 € per month. See the pricing page.

Which payload should you send?

JSON recordZPL envelope
Sender needs to knowfield namesthe full label layout
Layout changesedit the design, senders unchangedevery sender must be redeployed
Per-message printersubscription's printerprinter, incl. * broadcast
Fonts, barcodes, RFIDhandled by the designyour responsibility

Send records if you can. The design stays editable by the people who own the label, and a layout change does not require a release in the system that publishes the messages. Send envelopes when the ZPL comes out of a system you do not control, or when a message must reach several printers at once.

Related

More articles